[
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n    branches: [\"*\"]\n  pull_request:\n\njobs:\n  lint-build-test:\n    runs-on: macos-latest\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Select Xcode 26.1.1 (if present) or fallback to default\n        run: |\n          set -euo pipefail\n          for candidate in /Applications/Xcode_26.1.1.app /Applications/Xcode_26.1.app /Applications/Xcode.app; do\n            if [[ -d \"$candidate\" ]]; then\n              sudo xcode-select -s \"${candidate}/Contents/Developer\"\n              echo \"DEVELOPER_DIR=${candidate}/Contents/Developer\" >> \"$GITHUB_ENV\"\n              break\n            fi\n          done\n          /usr/bin/xcodebuild -version\n\n      - name: Swift toolchain version\n        run: |\n          set -euo pipefail\n          swift --version\n          swift package --version\n\n      - name: Install lint tools\n        run: ./Scripts/install_lint_tools.sh\n\n      - name: Lint\n        run: ./Scripts/lint.sh lint\n\n      - name: Swift Test\n        run: swift test --no-parallel\n\n  build-linux-cli:\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - name: linux-x64\n            runs-on: ubuntu-24.04\n          - name: linux-arm64\n            runs-on: ubuntu-24.04-arm\n    runs-on: ${{ matrix.runs-on }}\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Runner info\n        run: |\n          set -euo pipefail\n          uname -a\n          uname -m\n\n      - name: Setup Swift 6.2.1\n        uses: swift-actions/setup-swift@v3\n        with:\n          swift-version: \"6.2.1\"\n          skip-verify-signature: true\n\n      - name: Build CodexBarCLI (release, static Swift stdlib)\n        run: swift build -c release --product CodexBarCLI --static-swift-stdlib\n\n      - name: Swift Test (Linux only)\n        run: swift test --parallel\n\n      - name: Smoke test CodexBarCLI\n        shell: bash\n        run: |\n          set -euo pipefail\n          BIN_DIR=\"$(swift build -c release --product CodexBarCLI --static-swift-stdlib --show-bin-path)\"\n          BIN=\"$BIN_DIR/CodexBarCLI\"\n          \"$BIN\" --help >/dev/null\n          \"$BIN\" --version >/dev/null\n          if \"$BIN\" usage --provider codex --web >/dev/null 2>&1; then\n            echo \"Expected --web to fail on Linux\"\n            exit 1\n          fi\n          \"$BIN\" usage --provider codex --web 2>&1 | tee /tmp/codexbarcli-stderr.txt >/dev/null || true\n          grep -q \"macOS\" /tmp/codexbarcli-stderr.txt\n"
  },
  {
    "path": ".github/workflows/release-cli.yml",
    "content": "name: Release Linux CLI\n\non:\n  release:\n    types: [published]\n  workflow_dispatch:\n\npermissions:\n  contents: write\n\njobs:\n  build-linux-cli:\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - name: linux-x64\n            runs-on: ubuntu-24.04\n          - name: linux-arm64\n            runs-on: ubuntu-24.04-arm\n    runs-on: ${{ matrix.runs-on }}\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Runner info\n        run: |\n          set -euo pipefail\n          uname -a\n          uname -m\n\n      - name: Setup Swift 6.2.1\n        uses: swift-actions/setup-swift@v3\n        with:\n          swift-version: \"6.2.1\"\n          skip-verify-signature: true\n\n      - name: Build CodexBarCLI (release)\n        run: swift build -c release --product CodexBarCLI --static-swift-stdlib\n\n      - name: Package\n        id: pkg\n        shell: bash\n        run: |\n          set -euo pipefail\n\n          TAG=\"${GITHUB_REF_NAME}\"\n          if [[ -z \"$TAG\" ]]; then\n            echo \"Missing tag (GITHUB_REF_NAME).\" >&2\n            exit 1\n          fi\n\n          ARCH=\"$(uname -m)\"\n          case \"$ARCH\" in\n            x86_64) ARCH=\"x86_64\" ;;\n            aarch64|arm64) ARCH=\"aarch64\" ;;\n          esac\n\n          BIN_DIR=\"$(swift build -c release --product CodexBarCLI --static-swift-stdlib --show-bin-path)\"\n          OUT_DIR=\"$(mktemp -d)\"\n          install -m 0755 \"$BIN_DIR/CodexBarCLI\" \"$OUT_DIR/CodexBarCLI\"\n          ln -s \"CodexBarCLI\" \"$OUT_DIR/codexbar\"\n\n          ASSET=\"CodexBarCLI-${TAG}-linux-${ARCH}.tar.gz\"\n          (cd \"$OUT_DIR\" && tar czf \"$ASSET\" CodexBarCLI codexbar)\n          sha256sum \"$OUT_DIR/$ASSET\" > \"$OUT_DIR/$ASSET.sha256\"\n\n          echo \"out_dir=$OUT_DIR\" >> \"$GITHUB_OUTPUT\"\n          echo \"asset=$ASSET\" >> \"$GITHUB_OUTPUT\"\n\n      - name: Upload release assets\n        if: github.event_name == 'release'\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        shell: bash\n        run: |\n          set -euo pipefail\n          TAG=\"${GITHUB_REF_NAME}\"\n          OUT_DIR=\"${{ steps.pkg.outputs.out_dir }}\"\n          ASSET=\"${{ steps.pkg.outputs.asset }}\"\n          gh release upload \"$TAG\" \"$OUT_DIR/$ASSET\" \"$OUT_DIR/$ASSET.sha256\" --clobber\n\n      - name: Upload workflow artifact (manual runs)\n        if: github.event_name != 'release'\n        uses: actions/upload-artifact@v6\n        with:\n          name: codexbar-linux-cli-${{ matrix.name }}\n          path: |\n            ${{ steps.pkg.outputs.out_dir }}/${{ steps.pkg.outputs.asset }}\n            ${{ steps.pkg.outputs.out_dir }}/${{ steps.pkg.outputs.asset }}.sha256\n"
  },
  {
    "path": ".github/workflows/upstream-monitor.yml",
    "content": "name: Monitor Upstream Changes\n\non:\n  schedule:\n    # Run Monday and Thursday at 9 AM UTC\n    - cron: '0 9 * * 1,4'\n  workflow_dispatch:\n    inputs:\n      target:\n        description: 'Which upstream to check'\n        required: false\n        default: 'all'\n        type: choice\n        options:\n          - all\n          - upstream\n          - quotio\n\njobs:\n  check-upstreams:\n    runs-on: ubuntu-latest\n    permissions:\n      issues: write\n      contents: read\n    \n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n      \n      - name: Configure git\n        run: |\n          git config --global user.name 'github-actions[bot]'\n          git config --global user.email 'github-actions[bot]@users.noreply.github.com'\n      \n      - name: Add upstream remotes\n        run: |\n          git remote add upstream https://github.com/steipete/CodexBar.git || true\n          git remote add quotio https://github.com/nguyenphutrong/quotio.git || true\n          git fetch upstream\n          git fetch quotio\n      \n      - name: Check for new commits\n        id: check\n        run: |\n          # Count new commits in upstream\n          UPSTREAM_NEW=$(git log --oneline main..upstream/main --no-merges 2>/dev/null | wc -l | tr -d ' ')\n          echo \"upstream_commits=$UPSTREAM_NEW\" >> $GITHUB_OUTPUT\n          \n          # Count new commits in quotio (last 7 days)\n          QUOTIO_NEW=$(git log --oneline --all --remotes=quotio/main --since=\"7 days ago\" 2>/dev/null | wc -l | tr -d ' ')\n          echo \"quotio_commits=$QUOTIO_NEW\" >> $GITHUB_OUTPUT\n          \n          # Get commit summaries\n          echo \"upstream_summary<<EOF\" >> $GITHUB_OUTPUT\n          git log --oneline main..upstream/main --no-merges 2>/dev/null | head -10 >> $GITHUB_OUTPUT || echo \"No commits\" >> $GITHUB_OUTPUT\n          echo \"EOF\" >> $GITHUB_OUTPUT\n          \n          echo \"quotio_summary<<EOF\" >> $GITHUB_OUTPUT\n          git log --oneline --remotes=quotio/main --since=\"7 days ago\" 2>/dev/null | head -10 >> $GITHUB_OUTPUT || echo \"No commits\" >> $GITHUB_OUTPUT\n          echo \"EOF\" >> $GITHUB_OUTPUT\n      \n      - name: Create or update issue\n        if: steps.check.outputs.upstream_commits > 0 || steps.check.outputs.quotio_commits > 0\n        uses: actions/github-script@v8\n        with:\n          script: |\n            const upstreamCommits = '${{ steps.check.outputs.upstream_commits }}';\n            const quotioCommits = '${{ steps.check.outputs.quotio_commits }}';\n            const upstreamSummary = `${{ steps.check.outputs.upstream_summary }}`;\n            const quotioSummary = `${{ steps.check.outputs.quotio_summary }}`;\n            \n            const body = `## 🔄 Upstream Changes Detected\n            \n            **steipete/CodexBar:** ${upstreamCommits} new commits\n            **quotio:** ${quotioCommits} new commits (last 7 days)\n            \n            ### steipete/CodexBar Recent Commits\n            \\`\\`\\`\n            ${upstreamSummary}\n            \\`\\`\\`\n            \n            ### quotio Recent Commits\n            \\`\\`\\`\n            ${quotioSummary}\n            \\`\\`\\`\n            \n            ### 📋 Review Actions\n            \n            **Review upstream changes:**\n            \\`\\`\\`bash\n            ./Scripts/review_upstream.sh upstream\n            \\`\\`\\`\n            \n            **Review quotio changes:**\n            \\`\\`\\`bash\n            ./Scripts/analyze_quotio.sh\n            \\`\\`\\`\n            \n            **View detailed diffs:**\n            \\`\\`\\`bash\n            git diff main..upstream/main\n            git log -p quotio/main --since='7 days ago'\n            \\`\\`\\`\n            \n            ### 🔗 Links\n            - [steipete commits](https://github.com/steipete/CodexBar/compare/${context.sha}...steipete:CodexBar:main)\n            - [quotio commits](https://github.com/nguyenphutrong/quotio/commits/main)\n            \n            ---\n            *Auto-generated by upstream-monitor workflow*\n            *Last checked: ${new Date().toISOString()}*`;\n            \n            // Check for existing open issue\n            const issues = await github.rest.issues.listForRepo({\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n              labels: 'upstream-sync',\n              state: 'open'\n            });\n            \n            if (issues.data.length > 0) {\n              // Update existing issue\n              await github.rest.issues.update({\n                owner: context.repo.owner,\n                repo: context.repo.repo,\n                issue_number: issues.data[0].number,\n                body: body\n              });\n              \n              // Add comment\n              await github.rest.issues.createComment({\n                owner: context.repo.owner,\n                repo: context.repo.repo,\n                issue_number: issues.data[0].number,\n                body: `🔄 Updated with latest changes (${upstreamCommits} upstream, ${quotioCommits} quotio)`\n              });\n            } else {\n              // Create new issue\n              await github.rest.issues.create({\n                owner: context.repo.owner,\n                repo: context.repo.repo,\n                title: '🔄 Upstream Changes Available for Review',\n                body: body,\n                labels: ['upstream-sync', 'needs-review']\n              });\n            }\n      \n      - name: No changes detected\n        if: steps.check.outputs.upstream_commits == 0 && steps.check.outputs.quotio_commits == 0\n        run: |\n          echo \"✅ No new upstream changes detected\"\n          echo \"steipete/CodexBar: up to date\"\n          echo \"quotio: no commits in last 7 days\"\n\n"
  },
  {
    "path": ".gitignore",
    "content": "# Xcode user/session\nxcuserdata/\n.swiftpm/xcode/xcshareddata/\n.codexbar/config.json\n*.env\n*.local\n\n# Build products\n.build/\nDerivedData\n\n# Bundles / artifacts\n# Main app bundle (any variation)\nCodexBar.app/\nCodexBar *.app/\nCodexBar_*.app/\nCodexbar.app/\n# Release artifacts\n*.ipa\n*.dSYM*\n*.zip\n*.delta\n*.dmg\n*.pkg\n*.tar.gz\n*.tgz\nIcon.iconset\ndebug_*.swift\n\n# Misc\n.DS_Store\n.vscode/\n.codex/environments/\n.swiftpm-cache/\n\n# Debug/analysis docs\ndocs/*-analysis.md\ndocs/.astro/\n\n# Swift Package Manager metadata (leave sources tracked)\n# Packages/\n# Package.resolved\n"
  },
  {
    "path": ".swiftformat",
    "content": "# SwiftFormat configuration for Peekaboo project\n# Compatible with Swift 6 strict concurrency mode\n\n# IMPORTANT: Don't remove self where it's required for Swift 6 concurrency\n--self insert # Insert self for member references (required for Swift 6)\n--selfrequired # List of functions that require explicit self\n--importgrouping testable-bottom # Group @testable imports at the bottom\n--extensionacl on-declarations # Set ACL on extension members\n\n# Indentation\n--indent 4\n--indentcase false\n--ifdef no-indent\n--xcodeindentation enabled\n\n# Line breaks\n--linebreaks lf\n--maxwidth 120\n\n# Whitespace\n--trimwhitespace always\n--emptybraces no-space\n--nospaceoperators ...,..<\n--ranges no-space\n--someAny true\n\n# Wrapping\n--wraparguments before-first\n--wrapparameters before-first\n--wrapcollections before-first\n--closingparen same-line\n\n# Organization\n--organizetypes class,struct,enum,extension\n--extensionmark \"MARK: - %t + %p\"\n--marktypes always\n--markextensions always\n--structthreshold 0\n--enumthreshold 0\n\n# Swift 6 specific\n--swiftversion 6.2\n\n# Other\n--stripunusedargs closure-only\n--header ignore\n--allman false\n\n# Exclusions\n--exclude .build,.swiftpm,DerivedData,node_modules,dist,coverage,xcuserdata,Core/PeekabooCore/Sources/PeekabooCore/Extensions/NSArray+Extensions.swift\n"
  },
  {
    "path": ".swiftlint.yml",
    "content": "# SwiftLint configuration for Peekaboo - Swift 6 compatible\n\n# Paths to include\nincluded:\n  - Sources\n  - Tests\n\n# Paths to exclude\nexcluded:\n  - .build\n  - DerivedData\n  - \"**/Generated\"\n  - \"**/Resources\"\n  - \"**/.build\"\n  - \"**/Package.swift\"\n  - \"**/Tests/Resources\"\n  - \"Apps/CLI/.build\"\n  - \"**/DerivedData\"\n  - \"**/.swiftpm\"\n  - Pods\n  - Carthage\n  - fastlane\n  - vendor\n  - \"*.playground\"\n  # Exclude specific files that should not be linted/formatted\n  - \"Core/PeekabooCore/Sources/PeekabooCore/Extensions/NSArray+Extensions.swift\"\n\n# Analyzer rules (require compilation)\nanalyzer_rules:\n  - unused_declaration\n  - unused_import\n\n# Enable specific rules\nopt_in_rules:\n  - array_init\n  - closure_spacing\n  - contains_over_first_not_nil\n  - empty_count\n  - empty_string\n  - explicit_init\n  - fallthrough\n  - fatal_error_message\n  - first_where\n  - joined_default_parameter\n  - last_where\n  - literal_expression_end_indentation\n  - multiline_arguments\n  - multiline_parameters\n  - operator_usage_whitespace\n  - overridden_super_call\n  - pattern_matching_keywords\n  - private_outlet\n  - prohibited_super_call\n  - redundant_nil_coalescing\n  - sorted_first_last\n  - switch_case_alignment\n  - unneeded_parentheses_in_closure_argument\n  - vertical_parameter_alignment_on_call\n\n# Disable rules that conflict with Swift 6 or our coding style\ndisabled_rules:\n  # Swift 6 requires explicit self - disable explicit_self rule\n  - explicit_self\n  \n  # SwiftFormat handles these\n  - trailing_whitespace\n  - trailing_newline\n  - trailing_comma\n  - vertical_whitespace\n  - indentation_width\n  \n  # Too restrictive or not applicable\n  - identifier_name # Single letter names are fine in many contexts\n  - file_header\n  - explicit_top_level_acl\n  - explicit_acl\n  - explicit_type_interface\n  - missing_docs\n  - required_deinit\n  - prefer_nimble\n  - quick_discouraged_call\n  - quick_discouraged_focused_test\n  - quick_discouraged_pending_test\n  - anonymous_argument_in_multiline_closure\n  - no_extension_access_modifier\n  - no_grouping_extension\n  - switch_case_on_newline\n  - strict_fileprivate\n  - extension_access_modifier\n  - convenience_type\n  - no_magic_numbers\n  - one_declaration_per_file\n  - vertical_whitespace_between_cases\n  - vertical_whitespace_closing_braces\n  - superfluous_else\n  - number_separator\n  - prefixed_toplevel_constant\n  - opening_brace\n  - trailing_closure\n  - contrasted_opening_brace\n  - sorted_imports\n  - redundant_type_annotation\n  - shorthand_optional_binding\n  - untyped_error_in_catch\n  - file_name\n  - todo\n  \n# Rule configurations\nforce_cast: warning\nforce_try: warning\n\n# identifier_name rule disabled - see disabled_rules section\n\ntype_name:\n  min_length:\n    warning: 2\n    error: 1\n  max_length:\n    warning: 60\n    error: 80\n\nfunction_body_length:\n  warning: 150\n  error: 300\n\nfile_length:\n  warning: 1500\n  error: 2500\n  ignore_comment_only_lines: true\n\ntype_body_length:\n  warning: 800\n  error: 1200\n\ncyclomatic_complexity:\n  warning: 20\n  error: 120\n\nlarge_tuple:\n  warning: 4\n  error: 5\n\nnesting:\n  type_level:\n    warning: 4\n    error: 6\n  function_level:\n    warning: 5\n    error: 7\n\nline_length:\n  warning: 120\n  error: 250\n  ignores_comments: true\n  ignores_urls: true\n\n# Custom rules can be added here if needed\n\n# Reporter type\nreporter: \"xcode\"\n"
  },
  {
    "path": ".swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:\">\n   </FileRef>\n</Workspace>\n"
  },
  {
    "path": "AGENTS.md",
    "content": "# Repository Guidelines\n\n## Project Structure & Modules\n- `Sources/CodexBar`: Swift 6 menu bar app (usage/credits probes, icon renderer, settings). Keep changes small and reuse existing helpers.\n- `Tests/CodexBarTests`: XCTest coverage for usage parsing, status probes, icon patterns; mirror new logic with focused tests.\n- `Scripts`: build/package helpers (`package_app.sh`, `sign-and-notarize.sh`, `make_appcast.sh`, `build_icon.sh`, `compile_and_run.sh`).\n- `docs`: release notes and process (`docs/RELEASING.md`, screenshots). Root-level zips/appcast are generated artifacts—avoid editing except during releases.\n\n## Build, Test, Run\n- Dev loop: `./Scripts/compile_and_run.sh` kills old instances, runs `swift build` + `swift test`, packages, relaunches `CodexBar.app`, and confirms it stays running.\n- Quick build/test: `swift build` (debug) or `swift build -c release`; `swift test` for the full XCTest suite.\n- Package locally: `./Scripts/package_app.sh` to refresh `CodexBar.app`, then restart with `pkill -x CodexBar || pkill -f CodexBar.app || true; cd /Users/steipete/Projects/codexbar && open -n /Users/steipete/Projects/codexbar/CodexBar.app`.\n- Release flow: `./Scripts/sign-and-notarize.sh` (arm64 notarized zip) and `./Scripts/make_appcast.sh <zip> <feed-url>`; follow validation steps in `docs/RELEASING.md`.\n\n## Coding Style & Naming\n- Enforce SwiftFormat/SwiftLint: run `swiftformat Sources Tests` and `swiftlint --strict`. 4-space indent, 120-char lines, explicit `self` is intentional—do not remove.\n- Favor small, typed structs/enums; maintain existing `MARK` organization. Use descriptive symbols; match current commit tone.\n\n## Testing Guidelines\n- Add/extend XCTest cases under `Tests/CodexBarTests/*Tests.swift` (`FeatureNameTests` with `test_caseDescription` methods).\n- Always run `swift test` (or `./Scripts/compile_and_run.sh`) before handoff; add fixtures for new parsing/formatting scenarios.\n- After any code change, run `pnpm check` and fix all reported format/lint issues before handoff.\n\n## Commit & PR Guidelines\n- Commit messages: short imperative clauses (e.g., “Improve usage probe”, “Fix icon dimming”); keep commits scoped.\n- PRs/patches should list summary, commands run, screenshots/GIFs for UI changes, and linked issue/reference when relevant.\n\n## Agent Notes\n- Use the provided scripts and package manager (SwiftPM); avoid adding dependencies or tooling without confirmation.\n- Validate behavior against the freshly built bundle; restart via the pkill+open command above to avoid running stale binaries.\n- To guarantee the right bundle is running after a rebuild, use: `pkill -x CodexBar || pkill -f CodexBar.app || true; cd /Users/steipete/Projects/codexbar && open -n /Users/steipete/Projects/codexbar/CodexBar.app`.\n- After any code change that affects the app, always rebuild with `Scripts/package_app.sh` and restart the app using the command above before validating behavior.\n- If you edited code, run `scripts/compile_and_run.sh` before handoff; it kills old instances, builds, tests, packages, relaunches, and verifies the app stays running.\n- Per user request: after every edit (code or docs), rebuild and restart using `./Scripts/compile_and_run.sh` so the running app reflects the latest changes.\n- Release script: keep it in the foreground; do not background it—wait until it finishes.\n- Release keys: find in `~/.profile` if missing (Sparkle + App Store Connect).\n- Prefer modern SwiftUI/Observation macros: use `@Observable` models with `@State` ownership and `@Bindable` in views; avoid `ObservableObject`, `@ObservedObject`, and `@StateObject`.\n- Favor modern macOS 15+ APIs over legacy/deprecated counterparts when refactoring (Observation, new display link APIs, updated menu item styling, etc.).\n- Keep provider data siloed: when rendering usage or account info for a provider (Claude vs Codex), never display identity/plan fields sourced from a different provider.***\n- Claude CLI status line is custom + user-configurable; never rely on it for usage parsing.\n- Cookie imports: default Chrome-only when possible to avoid other browser prompts; override via browser list when needed.\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## Unreleased\n### Highlights\n- Add Alibaba Coding Plan provider with region-aware quota fetching, widget integration, and browser-cookie import defaults (#574).\n- Add GPT-5.4 mini and nano pricing (#561). Thanks @iam-brain!\n- Add per-model token counts to cost history (#546). Thanks @iam-brain!\n- Refactor the Claude provider end to end into clearer, better-tested components while preserving behavior (#494).\n\n### Providers & Usage\n- Alibaba: add Coding Plan provider support with region-aware web/API quota fetching, widget integration, and browser-cookie import defaults (#574).\n- Claude: refactor the provider end to end into clearer components, with baseline docs and expanded tests to lock down behavior (#494).\n- Codex: add GPT-5.4 mini and nano pricing (#561). Thanks @iam-brain!\n- Cost history: add per-model token counts so token usage is broken out by model (#546). Thanks @iam-brain!\n\n### Menu & Settings\n- Menu: wrap long status blurbs and preserve wrapped titles for multiline entries (#543). Thanks @zkforge!\n\n## 0.18.0 — 2026-03-15\n### Highlights\n- Add Kilo provider support with API/CLI source modes, widget integration, and pass/credit handling (#454). Built on work by @coreh.\n- Add Ollama provider, including token-account support in Settings and CLI (#380). Thanks @CryptoSageSnr!\n- Add OpenRouter provider for credit-based usage tracking (#396). Thanks @chountalas!\n- Add Codex historical pace with risk forecasting, backfill, and zero-usage-day handling (#482, supersedes #438). Thanks @tristanmanchester!\n- Add a merged-menu Overview tab with configurable providers and row-to-provider navigation (#416). @ratulsarna\n- Add an experimental option to suppress Claude Keychain prompts (#388).\n- Reduce CPU/energy regressions and JSONL scanner overhead in Codex/web usage paths (#402, #392). Thanks @bald-ai and @asonawalla!\n\n### Providers & Usage\n- Codex: add historical pace risk forecasting and backfill, gate pace computation by display mode, and handle zero-usage days in historical data (#482, supersedes #438). Thanks @tristanmanchester!\n- Kilo: add provider support with source-mode fallback, clearer credential/login guidance, auto top-up activity labeling, zero-balance credit handling, and pass parsing/menu rendering (#454). Thanks @coreh!\n- Ollama: add provider support with token-account support in app/CLI, Chrome-default auto cookie import, and manual-cookie mode (#380). Thanks @CryptoSageSnr!\n- OpenRouter: add provider support with credit tracking, key-quota popup support, token-account labels, fallback status icons, and updated icon/color (#396). Thanks @chountalas!\n- Gemini: show separate Pro, Flash, and Flash Lite meters by splitting Gemini CLI quota buckets for `gemini-2.5-flash` and `gemini-2.5-flash-lite` (#496). Thanks @aladh\n- Codex: in percent display mode with \"show remaining,\" show remaining credits in the menu bar when session or weekly usage is exhausted (#336). Thanks @teron131!\n- Claude: surface rate-limit errors from the CLI `/usage` probe with a user-friendly message, and harden \"Failed to load usage data\" matching against whitespace-collapsed output.\n- Claude: restore weekly/Sonnet reset parsing from whitespace-collapsed CLI `/usage` output so reset times and pace details still appear after CLI fallback.\n- Claude: fix extra-usage double conversion so OAuth/Web values stay on a single normalization path (#472, supersedes #463). Thanks @Priyans-hu!\n- Claude: remove root-directory mtime short-circuiting in cost scanning so new session logs inside existing `~/.claude/projects/*` folders are discovered reliably (#462, fixes #411). Thanks @Priyans-hu!\n- Copilot: harden free-plan quota parsing and fallback behavior by treating underdetermined values as unknown, preserving missing metadata as nil (#432, supersedes #393). Thanks @emanuelst!\n- OpenCode: treat explicit `null` subscription responses as missing usage data, skip POST fallback, and return a clearer workspace-specific error (#412).\n- OpenCode: surface clearer HTTP errors. Thanks @SalimBinYousuf1!\n- Codex: preserve exact GPT-5 model IDs in local cost history, add GPT-5.4 pricing, and label zero-cost `gpt-5.3-codex-spark` sessions as \"Research Preview\" in cost breakdowns (#511). Thanks @iam-brain!\n- Augment: prevent refresh stalls when `auggie account status` hangs by replacing unbounded CLI waits with timed subprocess execution and fallback handling (#481). Thanks @bryant24hao!\n- Update Kiro parsing for `kiro-cli` 1.24+ / Q Developer formats and non-managed plan handling (#288). Thanks @kilhyeonjun!\n- Kimi: in automatic metric mode, prioritize the 5-hour rate-limit window for menu bar and merged highest-usage calculations (#390). Thanks @ajaxjiang96!\n- Browser cookie import: match Gecko `*.default*` profile directories case-insensitively so Firefox/Zen cookie detection works with uppercase `.Default` directories (#422). Thanks @bald-ai!\n- MiniMax: make both Settings \"Open Coding Plan\" actions region-aware so China mainland selection opens `platform.minimaxi.com` instead of the global domain (#426, fixes #378). Thanks @bald-ai!\n- Menu: rebuild the merged provider switcher when “Show usage as used” changes so switcher progress updates immediately (#306). Thanks @Flohhhhh!\n- Warp: update API key setup guidance.\n- Claude: update the \"not installed\" help link to the current Claude Code documentation URL (#431). Thanks @skebby11!\n- Fix Claude setup message package name (#376). Thanks @daegwang!\n\n### Menu & Settings\n- Merged menu: keep Merge Icons, the switcher, and Overview tied to user-enabled providers even when some providers are temporarily unavailable, while defaulting menu content and icon state to an available provider when possible (#525). Thanks @Astro-Han!\n- Merged menu: add an Overview switcher tab that shows up to three provider usage rows in provider order (#416).\n- Settings: add \"Overview tab providers\" controls to choose/deselect Overview providers, with persisted selection reconciliation as enabled providers change (#416).\n- Menu: hide contextual provider actions while Overview is selected and rebuild switcher state when overview availability changes (#416).\n\n### Claude OAuth & Keychain\n- Add an experimental Claude OAuth Security-CLI reader path and option in settings.\n- Apply stored prompt mode and fallback policy to silent/noninteractive keychain probes.\n- Add cooldown for background OAuth keychain retries.\n- Disable experimental toggle when keychain access is disabled.\n- Use a `claude-code/<version>` User-Agent for OAuth usage requests instead of a generic identifier.\n\n### Performance & Reliability\n- Codex/OpenAI web: reduce CPU and energy overhead by shortening failed CLI probe windows, capping web retry timeouts, and using adaptive idle blink scheduling (#402). Thanks @bald-ai!\n- Cost usage scanner: optimize JSONL chunk parsing to avoid buffer-front removal overhead on large logs (#392). Thanks @asonawalla!\n- TTY runner: fence shutdown registration to avoid launch/shutdown races, isolate process groups before shutdown rejection, and ensure lingering CLI descendants are cleaned up on app termination (#429). Thanks @uraimo!\n\n\n## 0.18.0-beta.3 — 2026-02-13\n### Highlights\n- Claude OAuth/keychain flows were reworked across a series of follow-up PRs to reduce prompt storms, stabilize background behavior, surface a setting to control prompt policy and make failure modes deterministic (#245, #305, #308, #309, #364). Thanks @manikv12!\n- Claude: harden Claude Code PTY capture for `/usage` and `/status` (prompt automation, safer command palette confirmation, partial UTF-8 handling, and parsing guards against status-bar context meters) (#320).\n- New provider: Warp (credits + add-on credits) (#352). Thanks @Kathie-yu!\n- Provider correctness fixes landed for Cursor plan parsing and MiniMax region routing (#240, #234, #344). Thanks @robinebers and @theglove44!\n- Menu bar animation behavior was hardened in merged mode and fallback mode (#283, #291). Thanks @vignesh07 and @Ilakiancs!\n- CI/tooling reliability improved via pinned lint tools, deterministic macOS test execution, and PTY timing test stabilization plus Node 24-ready GitHub Actions upgrades (#292, #312, #290).\n\n### Claude OAuth & Keychain\n- Claude OAuth creds are cached in CodexBar Keychain to reduce repeated prompts.\n- Prompts can still appear when Claude OAuth credentials are expired, invalid, or missing and re-auth is required.\n- In Auto mode, background refresh keeps prompts suppressed; interactive prompts are limited to user actions (menu open or manual refresh).\n- OAuth-only mode remains strict (no silent Web/CLI fallback); Auto mode may do one delegated CLI refresh + one OAuth retry before falling back.\n- Preferences now expose a Claude Keychain prompt policy (Never / Only on user action / Always allow prompts) under Providers → Claude; if global Keychain access is disabled in Advanced, this control remains visible but inactive.\n\n### Provider & Usage Fixes\n- Warp: add Warp provider support (credits + add-on credits), configurable via Settings or `WARP_API_KEY`/`WARP_TOKEN` (#352). Thanks @Kathie-yu!\n- Cursor: compute usage against `plan.limit` rather than `breakdown.total` to avoid incorrect limit interpretation (#240). Thanks @robinebers!\n- MiniMax: correct API region URL selection to route requests to the expected regional endpoint (#234). Thanks @theglove44!\n- MiniMax: always show the API region picker and retry the China endpoint when the global host rejects the token to avoid upgrade regressions for users without a persisted region (#344). Thanks @apoorvdarshan!\n- Claude: add Opus 4.6 pricing so token cost scanning tracks USD consumed correctly (#348). Thanks @arandaschimpf!\n- z.ai: handle quota responses with missing token-limit fields, avoid incorrect used-percent calculations, and harden empty-response behavior with safer logging (#346). Thanks @MohamedMohana and @halilertekin!\n- z.ai: fix provider visibility in the menu when enabled with token-account credentials (availability now considers the effective fetch environment).\n- Amp: detect login redirects during usage fetch and fail fast when the session is invalid (#339). Thanks @JosephDoUrden!\n- Resource loading: fix app bundle lookup path to avoid \"could not load resource bundle\" startup failures (#223). Thanks @validatedev!\n- OpenAI Web dashboard: keep WebView instances cached for reuse to reduce repeated network fetch overhead; tests were updated to avoid network-dependent flakes (#284). Thanks @vignesh07!\n- Token-account precedence: selected token account env injection now correctly overrides provider config `apiKey` values in app and CLI environments. Thanks @arvindcr4!\n- Claude: make Claude CLI probing more resilient by scoping auto-input to the active subcommand and trimming to the latest Usage panel before parsing to avoid false matches from earlier screen fragments (#320).\n\n### Menu Bar & UI Behavior\n- Prevent fallback-provider loading animation loops (battery/CPU drain when no providers are enabled) (#283). Thanks @vignesh07!\n- Prevent status overlay rendering for disabled providers while in merged mode (#291). Thanks @Ilakiancs!\n\n### CI, Tooling & Test Stability\n- Pin SwiftFormat/SwiftLint versions and harden lint installer behavior (version drift + temp-file leak fixes) (#292).\n- Use more deterministic macOS CI test settings (including non-parallel paths where needed) and align runner/toolchain behavior for stability (#292).\n- Stabilize PTY command timing tests to reduce CI flakiness (#312).\n- Upgrade `actions/checkout` to v6 and `actions/github-script` to v8 for Node 24 compatibility in `upstream-monitor.yml` (#290). Thanks @salmanmkc!\n- Tests: add TaskLocal-based keychain/cache overrides so keychain gating and KeychainCacheStore test stores do not leak across concurrent test execution (#320).\n\n### Docs & Maintenance\n- Update docs for Claude data fetch behavior and keychain troubleshooting notes.\n- Update MIT license year.\n\n## 0.18.0-beta.2 — 2026-01-21\n### Highlights\n- OpenAI web dashboard refresh cadence now follows 5× the base refresh interval.\n- OpenAI web dashboard WebView is kept warm between scrapes to avoid repeated SPA downloads while idle CPU stays low (#284). Thanks @vignesh07!\n- Menu bar: avoid fallback animation loop when all providers are disabled (#283). Thanks @vignesh07!\n- Codex settings now include a toggle to disable OpenAI web extras.\n\n### Providers\n- Providers: add Dia browser support across cookie import and profile detection (#209). Thanks @validatedev!\n- Codex: include archived session logs in local token cost scanning and dedupe by session id.\n- Claude: harden CLI /usage parsing and avoid ANTHROPIC_* env interference during probes.\n\n### Menu & Menu Bar\n- Menu: opening OpenAI web submenus triggers a refresh when the data is stale.\n- Menu: fix usage line labels to honor “Show usage as used”.\n- Debug: add a toggle to keep Codex/Claude CLI sessions alive between probes.\n- Debug: add a button to reset CLI probe sessions.\n- App icon: use the classic icon on macOS 15 and earlier while keeping Liquid Glass for macOS 26+ (#178). Thanks @zerone0x!\n\n## 0.18.0-beta.1 — 2026-01-18\n### Highlights\n- New providers: OpenCode (web usage), Vertex AI, Kiro, Kimi, Kimi K2, Augment, Amp, Synthetic.\n- Provider source controls: usage source pickers for Codex/Claude, manual cookie headers, cookie caching with source/timestamp.\n- Menu bar upgrades: display mode picker (percent/pace/both), auto-select near limit, absolute reset times, pace summary line.\n- CLI/config revamp: config-backed provider settings, JSON-only errors, config validate/dump.\n\n### Providers\n- OpenCode: add web usage provider with workspace override + Chrome-first cookie import (#188). Thanks @anthnykr!\n- OpenCode: refresh provider logo (#190). Thanks @anthnykr!\n- Vertex AI: add provider with quota-based usage from gcloud ADC. Thanks @bahag-chaurasiak!\n- Vertex AI: token costs are shown via the Claude provider (same local logs).\n- Vertex AI: harden quota usage parsing for edge-case responses.\n- Kiro: add CLI-based usage provider via kiro-cli. Thanks @neror!\n- Kiro: clean up provider wiring and show plan name in the menu.\n- Kiro: harden CLI idle handling to avoid partial usage snapshots (#145). Thanks @chadneal!\n- Kimi: add usage provider with cookie-based API token stored in Keychain (#146). Thanks @rehanchrl!\n- Kimi K2: add API-key usage provider for credit totals (#147). Thanks @0-CYBERDYNE-SYSTEMS-0!\n- Augment: add provider with browser-cookie usage tracking.\n- Augment: prefer Auggie CLI usage with web fallback, plus session refresh + recovery tools (#142). Thanks @bcharleson!\n- Amp: add provider with Amp Free usage tracking (#167). Thanks @duailibe!\n- Synthetic: add API-key usage provider with quota snapshots (#171). Thanks @monotykamary!\n- JetBrains AI: include IDEs missing quota files, expand custom paths, and add Android Studio base paths (#194). Thanks @steipete!\n- JetBrains AI: detect IDE directories case-insensitively (#200). Thanks @zerone0x!\n- Cursor: support legacy request-based plans and show individual on-demand usage (#125) — thanks @vltansky\n- Cursor: avoid Intel crash when opening login and harden WebKit teardown. Thanks @meghanto!\n- Cursor: load stored session cookies before reads to make relaunches deterministic.\n- z.ai: add BigModel CN region option for API endpoint selection (#140). Thanks @nailuoGG!\n- MiniMax: add China mainland region option + host overrides (#143). Thanks @nailuoGG!\n- MiniMax: support API token or cookie auth; API token takes precedence and hides cookie UI (#149). Thanks @aonsyed!\n- Gemini: prefer loadCodeAssist project IDs for quota fetches (#172). Thanks @lolwierd!\n- Gemini: honor loadCodeAssist project IDs for quota + support Nix CLI layout (#184). Thanks @HaukeSchnau!\n- Claude: fix OAuth “Extra usage” spend/limit units when the API returns minor currency units (#97).\n- Claude: rescale extra usage costs when plan hints are missing and prefer web plan hints for extras (#181). Thanks @jorda0mega!\n- Usage formatting: fix currency parsing/formatting on non-US locales (e.g., pt-BR). Thanks @mneves75!\n\n### Provider Sources & Security\n- Providers: cache browser cookies in Keychain (per provider) and show cached source/time in settings.\n- Codex/Claude/Cursor/Factory/MiniMax: cookie sources now include Manual (paste a Cookie header) in addition to Automatic.\n- Codex/Claude/Cursor/Factory/MiniMax: skip cookie imports from browsers without usable cookie stores (profile/cookie DB) to avoid unnecessary Keychain prompts.\n- Providers: suppress repeated Chromium Keychain prompts after access denied and honor disabled Keychain access.\n\n### Preferences & Settings\n- Preferences: swap provider refresh button and enable toggle order.\n- Preferences: animate settings width and widen Providers on selection.\n- Preferences: shrink default settings size and reduce overall height.\n- Preferences: move “Hide personal information” to Advanced.\n- Providers: shorten fetch subtitle to relative time only.\n- Preferences: soften provider sidebar background and stabilize drag reordering.\n- Preferences: restrict provider drag handle to handle-only.\n- Preferences: move provider refresh timing to a dedicated second line.\n- Preferences: tighten provider usage metrics spacing.\n- Preferences: show refresh timing inline in provider detail subtitle.\n- Preferences: move “Access OpenAI via web” into Providers → Codex.\n- Preferences: add usage source pickers for Codex + Claude with auto fallback.\n- Preferences: add cookie source pickers with contextual helper text for the selected mode.\n- Preferences: move “Disable Keychain access” to Advanced and require manual cookies when enabled.\n- Preferences: add per-provider menu bar metric picker (#185) — thanks @HaukeSchnau\n- Preferences: tighten provider rows (inline pickers, compact layout, inline refresh + auto-source status).\n- Preferences: remove the “experimental” label from Antigravity.\n\n### Menu & Menu Bar\n- Menu: add a toggle to show reset times as absolute clock values (instead of countdowns).\n- Menu: show an “Open Terminal” action when Claude OAuth fails.\n- Menu: add “Hide personal information” toggle and redact emails in menu UI (#137). Thanks @t3dotgg!\n- Menu: keep a pace summary line alongside the visual marker (#155). Thanks @antons!\n- Menu: reduce provider-switch flicker and avoid redundant menu card sizing for faster opens (#132). Thanks @ibehnam!\n- Menu: keep background refresh on open without forcing token usage (#158). Thanks @weequan93!\n- Menu: Cursor switcher shows On-Demand remaining when Plan is exhausted in show-remaining mode (#193). Thanks @vltansky!\n- Menu: avoid single-letter wraps in provider switcher titles.\n- Menu: widen provider switcher buttons to avoid clipped titles.\n- Menu bar: rebuild provider status items on reorder so icons update correctly.\n- Menu bar: optional auto-select provider closest to its rate limit and keep switcher progress visible (#159). Thanks @phillco!\n- Menu bar: add display mode picker for percent/pace/both in the menu bar icon (#169). Thanks @PhilETaylor!\n- Menu bar: fix combined loading indicator flicker during loading animation (incl. debug replay).\n- Menu bar: prevent blink updates from clobbering the loading animation.\n\n### CLI & Config\n- CLI: respect the reset time display setting.\n- CLI: add pink accents, usage bars, and weekly pace lines to text output.\n- CLI: add config-backed provider settings, `--json-only`, and `--source api` for key-based providers.\n- CLI: add `config validate`/`config dump` commands and per-provider JSON error payloads.\n- CLI/App: move provider secrets + ordering to `~/.codexbar/config.json` (no Keychain persistence).\n- Providers: resolve API tokens from config/env only (no Keychain fallback).\n\n### Dev & Tests\n- Dev: move Chromium profile discovery into SweetCookieKit (adds Helium net.imput.helium). Thanks @hhushhas!\n- Dev: bump SweetCookieKit to 0.2.0.\n- Dev: migrate stored Keychain items to reduce rebuild prompts.\n- Dev: move path debug snapshot off the main thread and debounce refreshes to avoid startup hitches (#131). Thanks @ibehnam!\n- Tests: expand Kiro CLI coverage.\n- Tests: stabilize Claude PTY integration cleanup and reset CLI sessions after probes.\n- Tests: kill leaked codex app-server after tests.\n- Tests: add regression coverage for merged loading icon layout stability.\n- Tests: cover config validation and JSON-only CLI errors.\n- Build: stabilize Swift test runtime.\n\n## 0.17.0 — 2025-12-31\n- New providers: MiniMax.\n- Keychain: show a preflight explanation before macOS prompts for OAuth tokens or cookie decryption.\n- Providers: defer z.ai + Copilot Keychain reads until the user interacts with the token field.\n- Menu bar: avoid status item menu reattachment and layout flips during refresh to reduce icon flicker.\n- Dev: align SweetCookieKit local-storage tests with Swift Testing.\n- Charts: align hover selection bands with visible bars in credits + usage breakdown history.\n- About: fix website link in the About panel. Thanks @felipeorlando!\n\n## 0.16.1 — 2025-12-29\n- Menu: reduce layout thrash when opening menus and sizing charts. Thanks @ibehnam!\n- Packaging: default release notarization builds universal (arm64 + x86_64) zip.\n- OpenAI web: reduce idle CPU by suspending cached WebViews when not scraping. Thanks @douglascamata!\n- Icons: switch provider brand icons to SVGs for sharper rendering. Thanks @vandamd!\n\n## 0.16.0 — 2025-12-29\n- Menu bar: optional “percent mode” (provider brand icons + percentage labels) via Advanced toggle.\n- CLI: add `codexbar cost` to print local cost usage (text/JSON) for Codex + Claude.\n- Cost: align local cost scanner with ccusage; stabilize parsing/decoding and handle large JSONL lines.\n- Claude: skip pricing for unknown models (tokens still tracked) to avoid hard-coded legacy prices.\n- Performance: reduce menu bar CPU usage by caching morph icons, skipping redundant status-item updates, and caching provider enablement/order during animations.\n- Menu: improve provider switcher hover contrast in light mode.\n- Icons: refresh Droid + Claude brand assets to better match menu sizing.\n- CI: avoid interactive login-shell probes to reduce noisy “CLI missing” errors.\n\n## 0.15.3 — 2025-12-28\n- Codex: default to OAuth usage API (ChatGPT backend) with CLI-only override in Debug.\n- Codex: map OAuth credits balance directly, avoiding web fallback for credits.\n- Preferences: add optional “Access OpenAI via web” toggle and show blended source labels when web extras are active.\n- Copilot: replace blocking auth wait dialog with a non-modal sheet to avoid stuck login.\n\n## 0.15.2 — 2025-12-28\n- Copilot: fix device-flow waiting modal to close reliably after auth (and avoid stuck waits).\n- Packaging: include the KeyboardShortcuts resource bundle to prevent Settings → Keyboard shortcut crashes in packaged builds.\n\n## 0.15.1 — 2025-12-28\n- Preferences: fix provider API key fields reusing the wrong input when switching rows.\n- Preferences: avoid Advanced tab crash when opening settings.\n\n## 0.15.0 — 2025-12-28\n- New providers: Droid (Factory), Cursor, z.ai, Copilot.\n- macOS: CodexBar now supports Intel Macs (x86_64 builds + Sonoma fallbacks). Thanks @epoyraz!\n- Droid (Factory): new provider with Standard + Premium usage via browser cookies, plus dashboard + status links. Thanks @shashank-factory!\n- Menu: allow multi-line error messages in the provider subtitle (up to 4 lines).\n- Menu: fix subtitle sizing for multi-line error states.\n- Menu: avoid clipping on multi-line error subtitles.\n- Menu: widen the menu card when 7+ providers are enabled.\n- Providers: Codex, Claude Code, Cursor, Gemini, Antigravity, z.ai.\n- Gemini: switch plan detection to loadCodeAssist tier lookup (Paid/Workspace/Free/Legacy). Thanks @381181295!\n- Codex: OpenAI web dashboard is now the primary source for usage + credits; CLI fallback only when no matching cookies exist.\n- Claude: prefer OAuth when credentials exist; fall back to web cookies or CLI (thanks @ibehnam).\n- CLI: replace `--web`/`--claude-source` with `--source` (auto/web/cli/oauth); auto falls back only when cookies are missing.\n- Homebrew: cask now installs the `codexbar` CLI symlink. Thanks @dalisoft!\n- Cursor: add new usage provider with browser cookie auth (cursor.com + cursor.sh), on-demand bar support, and dashboard access.\n- Cursor: keep stored sessions on transient failures; clear only on invalid auth.\n- z.ai: new provider support with Tokens + MCP usage bars and MCP details submenu; API token now lives in Preferences (stored in Keychain); usage bars respect the show-used toggle. Thanks @uwe-schwarz for the initial work!\n- Copilot: new GitHub Copilot provider with device flow login plus Premium + Chat usage bars (including CLI support). Thanks @roshan-c!\n- Preferences: fix Advanced Display checkboxes and move the Quit button to the bottom of General.\n- Preferences: hide “Augment Claude via web” unless Claude usage source is CLI; rename the cost toggle to “Show cost summary”.\n- Preferences: add an Advanced toggle to show/hide optional Codex Credits + Claude Extra usage sections (on by default).\n- Widgets: add a new “CodexBar Switcher” widget that lets you switch providers and remember the selection.\n- Menu: provider switcher now uses crisp brand icons with equal-width segments and a per-provider usage indicator.\n- Menu: tighten provider switcher sizing and increase spacing between label and weekly indicator bar.\n- Menu: provider switcher no longer forces a wider menu when many providers are enabled; segments clamp to the menu width.\n- Menu: provider switcher now aligns to the same horizontal padding grid as the menu cards when space allows.\n- Dev: `compile_and_run.sh` now force-kills old instances to avoid launching duplicates.\n- Dev: `compile_and_run.sh` now waits for slow launches (polling for the process).\n- Dev: `compile_and_run.sh` now launches a single app instance (no more extra windows).\n- CI: build/test Linux `CodexBarCLI` (x86_64 + aarch64) and publish release assets as `CodexBarCLI-<tag>-linux-<arch>.tar.gz` (+ `.sha256`).\n- CLI: add alias fallback for Codex/Claude detection when PATH lookups fail.\n- Providers: support Arc browser cookies for Factory/Droid (and other Chromium-based cookie imports).\n- Providers: support ChatGPT Atlas browser data for Chromium cookie imports.\n- Providers: accept Auth.js secure session cookies for Factory/Droid login detection.\n- Providers: accept Factory auth session cookies (session/access-token) for Droid.\n- Droid: surface Factory API errors instead of masking them as missing sessions.\n- Droid: retry auth without access-token cookies when Factory flags a stale token.\n- Droid: try all detected browser profiles before giving up.\n- Droid: fall back to auth.factory.ai endpoints when cookies live on the auth host.\n- Droid: use WorkOS refresh tokens from browser local storage when cookies fail.\n- Droid: read WorkOS refresh tokens from Safari local storage.\n- Droid: try stored/WorkOS tokens before Chrome cookies to reduce Chrome Safe Storage prompts.\n- Menu: provider switcher bars now track primary quotas (Plan/Tokens/Pro), with Premium shown for Droid.\n- Menu: avoid duplicate summary blocks when a provider has no action rows.\n- OpenAI web: ignore cookie sets without session tokens to avoid false-positive dashboard fetches.\n- Providers: hide z.ai in the menu until an API key is set.\n- Menu: refresh runs automatically when opening the menu with a short retry (refresh row removed).\n- Menu: hide the Status Page row when a provider has no status URL.\n- Menu: align switcher bar with the “show usage as used” toggle.\n- Antigravity: fix lsof port filtering by ANDing listen + pid conditions. Thanks @shaw-baobao!\n- Claude: default to Claude Code OAuth usage API (credentials from Keychain or `~/.claude/.credentials.json`), with Debug selector + `--claude-source` CLI override (OAuth/Web/CLI).\n- OpenAI web: allow importing any signed-in browser session when Codex email is unknown (first-run friendly).\n- Core: Linux CLI builds now compile (mac-only WebKit/logging gated; FoundationNetworking imports where needed).\n- Core: fix CI flake for Claude trust prompts by making PTY writes fully reliable.\n- Core: Cursor provider is macOS-only (Linux CLI builds stub it).\n- Core: make `RateWindow` equatable (used by OpenAI dashboard snapshots and tests).\n- Tests: cover alias fallback resolution for Codex/Claude and add Linux platform gating coverage (run in CI).\n- Tests: cover hiding Codex Credits + Claude Extra usage via the Advanced toggle.\n- Docs: expand CLI docs for Linux install + flags.\n\n## 0.14.0 — 2025-12-25\n- New providers: Antigravity.\n- Antigravity: new local provider for the Antigravity language server (Claude + Gemini quotas) with an experimental toggle; improved plan display + debug output; clearer not-running/port errors; hide account switch.\n- Status: poll Google Workspace incidents for Gemini + Antigravity; Status Page opens the Workspace status page.\n- Settings: add Providers tab; move ccusage + status toggles to General; keep display controls in Advanced.\n- Menu/UI: widen the menu for four providers; cards/charts adapt to menu width; tighten provider switcher/toggle spacing; keep menus refreshed while open.\n- Gemini: hide the dashboard action when unsupported.\n- Claude: fix Extra usage spend/limit units (cents); improve CLI probe stability; surface web session info in Debug.\n- OpenAI web: fix dashboard ghost overlay on desktop (WebKit keepalive window).\n- Debug: add a debug-lldb build mode for troubleshooting.\n\n## 0.13.0 — 2025-12-24\n- Claude: add optional web-first usage via Safari/Chrome cookies (no CLI fallback) including “Extra usage” budget bar.\n- Claude: web identity now uses `/api/account` for email + plan (via rate_limit_tier).\n- Settings: standardize “Augment … via web” copy for Codex + Claude web cookie features.\n- Debug: Claude dump now shows web strategy, cookie discovery, HTTP status codes, and parsed summary.\n- Dev: add Claude web probe CLI to enumerate endpoints/fields using browser cookies.\n- Tests: add unit coverage for Claude web API usage, overage, and account parsing.\n- Menu: custom menu items now use the native selection highlight color (plus matching selection text/track colors).\n- Charts: boost hover highlight contrast for credits/usage history bands.\n- Menu: reorder Codex blocks to show credits before cost.\n- Menu: split Claude “Extra usage” (no submenu) from “Cost” (history submenu) and trim redundant extra-usage subtext.\n\n## 0.12.0 — 2025-12-23\n- Widgets: add WidgetKit extension backed by a shared app‑group usage snapshot.\n- New local cost usage tracking (Codex + Claude) via a lightweight scanner — inspired by ccusage (MIT). Computes cost from local JSONL logs without Node CLIs. Thanks @ryoppippi!\n- Cost summary now includes last‑30‑days tokens; weekly pace indicators (with runout copy) hide when usage is fully depleted. Thanks @Remedy92!\n- Claude: PTY probes now stop after idle, auto‑clean on restart, and run under a watchdog to avoid runaway CLI processes.\n- Menu polish: group history under card sections, simplify history labels, and refresh menus live while open.\n- Performance: faster usage log scanning + cost parsing; cache menu icons and speed up OpenAI dashboard parsing.\n- Sparkle: auto-download updates when auto-check is enabled, and only show the restart menu entry once an update is ready.\n- Widgets: experimental WidgetKit extension (may require restarting the widget gallery/Dock to appear).\n- Credits: show credits as a progress bar and add a credits history chart when OpenAI web data is available.\n- Credits: move “Buy Credits…” into its own menu item and improve auto-start checkout flow.\n\n## 0.11.2 — 2025-12-21\n- ccusage-codex cost fetch is faster and more reliable by limiting the session scan window.\n- Fix ccusage cost fetch hanging for large Codex histories by draining subprocess output while commands run.\n- Fix merged-icon loading animation when another provider is fetching (only the selected provider animates).\n- CLI PATH capture now uses an interactive login shell and merges with the app PATH, fixing missing Node/Codex/Claude/Gemini resolution for NVM-style installs.\n\n## 0.11.1 — 2025-12-21\n- Gemini OAuth token refresh now supports Bun/npm installations. Thanks @ben-vargas!\n\n## 0.11.0 — 2025-12-21\n- New optional cost display in the menu (session + last 30 days), powered by ccusage. Thanks @Xuanwo!\n- Fix loading-state card spacing to avoid double separators.\n\n## 0.10.0 — 2025-12-20\n- Gemini provider support (usage, plan detection, login flow). Thanks @381181295!\n- Unified menu bar icon mode with a provider switcher and Merge Icons toggle (default on when multiple providers are enabled). Thanks @ibehnam!\n- Fix regression from 0.9.1 where CLI detection failed for some installs by restoring interactive login-shell PATH loading.\n\n## 0.9.1 — 2025-12-19\n- CLI resolution now uses the login shell PATH directly (no more heuristic path scanning), so Codex/Claude match your shell config reliably.\n\n## 0.9.0 — 2025-12-19\n- New optional OpenAI web access: reuses your signed-in Safari/Chrome session to show **Code review remaining**, **Usage breakdown**, and **Credits usage history** in the menu (no credentials stored).\n- Credits still come from the Codex CLI; OpenAI web access is only used for the dashboard extras above.\n- OpenAI web sessions auto-sync to the Codex CLI email, support multiple accounts, and reset/re-import cookies on account switches to avoid stale cross-account data.\n- Fix Chrome cookie import (macOS 10): signed-in Chrome sessions are detected reliably (thanks @tobihagemann!).\n- Usage breakdown submenu: compact chart with hover details for day/service totals.\n- New “Show usage as used” toggle to invert progress bars (default remains “% left”, now in Advanced).\n- Session (5-hour) reset now shows a relative countdown (“Resets in 3h 31m”) in the menu card for Codex and Claude.\n- Claude: fix reset parsing so “Resets …” can’t be mis-attributed to the wrong window (session vs weekly).\n\n## 0.8.1 — 2025-12-17\n- Claude trust prompts (“Do you trust the files in this folder?”) are now auto-accepted during probes to prevent stuck refreshes. Thanks @tobihagemann!\n\n## 0.8.0 — 2025-12-17\n- CodexBar is now available via Homebrew: `brew install --cask steipete/tap/codexbar` (updates via `brew upgrade --cask steipete/tap/codexbar`).\n- Added session quota notifications for the sliding 5-hour window (Codex + Claude): notifies when it hits 0% and when it’s available again, based only on observed refresh data (including startup when already depleted). Thanks @GKannanDev!\n\n## 0.7.3 — 2025-12-17\n- Claude Enterprise accounts whose Claude Code `/usage` panel only shows “Current session” no longer fail parsing; weekly usage is treated as unavailable (fixes #19).\n\n## 0.7.2 — 2025-12-13\n- Claude “Open Dashboard” now routes subscription accounts (Max/Pro/Ultra/Team) to the usage page instead of the API console billing page. Thanks @auroraflux!\n- Codex/Claude binary resolution now detects mise/rtx installs (shims and newest installed tool version), fixing missing CLI detection for mise users. Thanks @philipp-spiess!\n- Claude usage/status probes now auto-accept the first-run “Ready to code here?” permission prompt (when launched from Finder), preventing timeouts and parse errors. Thanks @alexissan!\n- General preferences now surface full Codex/Claude fetch errors with one-click copy and expandable details, reducing first-run confusion when a CLI is missing.\n- Polished the menu bar “critter” icons: Claude is now a crisper, blockier pixel crab, and Codex has punchier eyes with reduced blurring in SwiftUI/menu rendering.\n\n## 0.7.1 — 2025-12-09\n- Menu bar icons now render on a true 18 pt/2× backing with pixel-aligned bars and overlays for noticeably crisper edges.\n- PTY runner now preserves the caller’s environment (HOME/TERM/bun installs) while enriching PATH, preventing Codex/Claude\n  probes from failing when CLIs are installed via bun/nvm or need their auth/config paths.\n- Added regression tests to lock in the enriched environment behavior.\n- Fixed a first-launch crash on macOS 26 caused by the 1×1 keepalive window triggering endless constraint updates; the hidden\n  window now uses a safe size and no longer spams SwiftUI state warnings.\n- Menu action rows now ship with SF Symbol icons (refresh, dashboard, status, settings, about, quit, copy error) for clearer at-a-glance affordances.\n- When the Codex CLI is missing, menu and CLI now surface an actionable install hint (`npm i -g @openai/codex` / bun) instead of a generic PATH error.\n- Node manager (nvm/fnm) resolution corrected so codex/claude binaries — and their `node` — are found reliably even when installed via fnm aliases or nvm defaults. Thanks @aliceisjustplaying for surfacing the gaps.\n- Login menu now shows phase-specific subtitles and disables interaction while running: “Requesting login…” while starting the CLI, then “Waiting in browser…” once the auth URL is printed; success still triggers the macOS notification.\n- Login state is tracked per provider so Codex and Claude icons/menus no longer share the same in-flight status when switching accounts.\n- Claude login PTY runner detects the auth URL without clearing buffers, keeps the session alive until confirmation, and exposes a Sendable phase callback used by the menu.\n- Claude CLI detection now includes Claude Code’s self-updating paths (`~/.claude/local/claude`, `~/.claude/bin/claude`) so PTY probes work even when only the bundled installer is used.\n\n## 0.7.0 — 2025-12-07\n- ✨ New rich menu card with inline progress bars and reset times for each provider, giving the menu a beautiful, at-a-glance dashboard feel (credit: Anton Sotkov @antons).\n\n## 0.6.1 — 2025-12-07\n- Claude CLI probes stop passing `--dangerously-skip-permissions`, aligning with the default permission prompt and avoiding hidden first-run failures.\n\n## 0.6.0 — 2025-12-04\n- New bundled CLI (`codexbar`) with single `usage` command, `--format text|json`, `--status`, and fast `-h/-V`.\n- CLI output now shows consistent headers (`Codex 0.x.y (codex-cli)`, `Claude Code <ver> (claude)`) and JSON includes `source` + `status`.\n- Advanced prefs install button symlinks `codexbar` into /usr/local/bin and /opt/homebrew/bin; docs refreshed.\n\n## 0.5.7 — 2025-11-26\n- Status Page and Usage Dashboard menu actions now honor the icon you click; Codex menus no longer open the Claude status site.\n\n## 0.5.6 — 2025-11-25\n- New playful “Surprise me” option adds occasional blinks/tilts/wiggles to the menu bar icons (one random effect at a time) plus a Debug “Blink now” trigger.\n- Preferences now include an Advanced tab (refresh cadence, Surprise me toggle, Debug visibility); window height trimmed ~20% for a tighter fit.\n- Motion timing eased and lengthened so blinks/wiggles feel smoother and less twitchy.\n\n## 0.5.5 — 2025-11-25\n- Claude usage scrape now recognizes the new “Current week (Sonnet only)” bar while keeping the legacy Opus label as a fallback.\n- Menu and docs now label the Claude tertiary limit as Sonnet to match the latest CLI wording.\n- PATH seeding now uses a deterministic binary locator plus a one-shot login-shell capture at startup (no globbed nvm paths); the Debug tab shows the resolved Codex binary and effective PATH layers.\n\n## 0.5.4 — 2025-11-24\n- Status blurb under “Status Page” no longer prefixes the text with “Status:”, keeping the incident description concise.\n- PTY runner now registers cleanup before launch so both ends of the TTY and the process group are torn down even when `Process.run()` throws (no leaked fds when spawn fails).\n\n## 0.5.3 — 2025-11-22\n- Added a per-provider “Status Page” menu item beneath Usage that opens the provider’s live status page (OpenAI or Claude).\n- Status API now refreshes alongside usage; incident states show a dot/! overlay on the status icon plus a status blurb under the menu item.\n- General preferences now include a default-on “Check provider status” toggle above refresh cadence.\n\n## 0.5.2 — 2025-11-22\n- Release packaging now includes uploading the dSYM archive alongside the app zip to aid crash symbolication (policy documented in the shared mac release guide).\n- Claude PTY fallback removed: Claude probes now rely solely on `script` stdout parsing, and the generic TTY runner is trimmed to Codex `/status` handling.\n- Fixed a busy-loop on the codex RPC stderr pipe (handler now detaches on EOF), eliminating the long-running high-CPU spin reported in issue #9.\n\n## 0.5.1 — 2025-11-22\n- Debug pane now exposes the Claude parse dump toggle, keeping the captured raw scrape in memory for inspection.\n- Claude About/debug views embed the current git hash so builds can be identified precisely.\n- Minor runtime robustness tweaks in the PTY runner and usage fetcher.\n\n## 0.5.0 — 2025-11-22\n- Codex usage/credits now use the codex app-server RPC by default (with PTY `/status` fallback when RPC is unavailable), reducing flakiness and speeding refreshes.\n- Codex CLI launches seed PATH with Homebrew/bun/npm/nvm/fnm defaults to avoid ENOENT in hardened/release builds; TTY probes reuse the same PATH.\n- Claude CLI probe now runs `/usage` and `/status` in parallel (no simulated typing), captures reset strings, and uses a resilient parser (label-first with ordered fallback) while keeping org/email separate by provider.\n- TTY runner now always tears down the spawned process group (even on early Claude login prompts) to avoid leaking CLI processes.\n- Default refresh cadence is now 5 minutes, and a 15-minute option was added to the settings picker.\n- Claude probes/version detection now start with `--allowed-tools \"\"` (tool access disabled) while keeping interactive PTY mode working.\n- Codex probes and version detection now launch the CLI with `-s read-only -a untrusted` to keep PTY runs sandboxed.\n- Codex warm-up screens (“data not available yet”) are handled gracefully: cached credits stay visible and the menu skips the scary parse error.\n- Codex reset times are shown for both RPC and TTY fallback, and plan labels are capitalized while emails stay verbatim.\n\n## 0.4.3 — 2025-11-21\n- Fix status item creation timing on macOS 15 by deferring NSStatusItem setup to after launch; adds a regression test for the path.\n- Menu bar icon with unknown usage now draws empty tracks (instead of a full bar when decorations are shown) by treating nil values as 0%.\n\n## 0.4.2 — 2025-11-21\n- Sparkle updates re-enabled in release builds (disabled only for the debug bundle ID).\n\n## 0.4.1 — 2025-11-21\n- Both Codex and Claude probes now run off the main thread (background PTY), avoiding menu/UI stalls during `/status` or `/usage` fetches.\n- Codex credits stay available even when `/status` times out: cached values are kept and errors are surfaced separately.\n- Claude/Codex provider autodetect runs on first launch (defaults to Codex if neither is installed) with a debug reset button.\n- Sparkle updates re-enabled in release builds (disabled only for debug bundle ID).\n- Claude probe now issues the `/usage` slash command directly to land on the Usage tab reliably and avoid palette misfires.\n\n## 0.4.0 — 2025-11-21\n- Claude Code support: dedicated Claude menu/icon plus dual-wired menus when both providers are enabled; shows email/org/plan and Sonnet usage with clickable errors.\n- New Preferences window: General/About tabs with provider toggles, refresh cadence, start-at-login, and always-on Quit.\n- Codex credits without web login: we now read `codex /status` in a PTY, auto-skip the update prompt, and parse session/weekly/credits; cached credits stay visible on transient timeouts.\n- Resilience: longer PTY timeouts, cached-credit fallback, one-line menu errors, and clearer parse/update messages.\n\n## 0.3.0 — 2025-11-18\n- Credits support: reads Codex CLI `/status` via PTY (no browser login), shows remaining credits inline, and moves history to a submenu.\n- Sign-in window with cookie reuse and a logout/clear-cookies action; waits out workspace picker and auto-navigates to usage page.\n- Menu: credits line bolded; login prompt hides once credits load; debug toggle always visible (HTML dump).\n- Icon: when weekly is empty, top bar becomes a thick credits bar (capped at 1k); otherwise bars stay 5h/weekly.\n\n## 0.2.2 — 2025-11-17\n- Menu bar icon stays static when no account/usage is present; loading animation only runs while fetching (12 fps) to keep idle CPU low.\n- Usage refresh first tails the newest session log (512 KB window) before scanning everything, reducing IO on large Codex logs.\n- Packaging/signing hardened: strip extended attributes, delete AppleDouble (`._*`) files, and re-sign Sparkle + app bundle to satisfy Gatekeeper.\n\n## 0.2.1 — 2025-11-17\n- Patch bump for refactor/relative-time changes; packaging scripts set to 0.2.1 (5).\n- Streamlined Codex usage parsing: modern rate-limit handling, flexible reset time parsing, and account rate-limit updates (thanks @jazzyalex and https://jazzyalex.github.io/agent-sessions/).\n\n## 0.2.0 — 2025-11-16\n- CADisplayLink-based loading animations (macOS 15 displayLink API) with randomized patterns (Knight Rider, Cylon, outside-in, race, pulse) and debug replay cycling through all.\n- Debug replay toggle (`defaults write com.steipete.codexbar debugMenuEnabled -bool YES`) to view every pattern.\n- Usage Dashboard link in menu; menu layout tweaked.\n- Updated time now shows relative formatting when fresher than 24h; refactored sources into smaller files for maintainability.\n- Version bumped to 0.2.0 (4).\n\n## 0.1.2 — 2025-11-16\n- Animated loading icon (dual bars sweep until usage arrives); always uses rendered template icon.\n- Sparkle embedding/signing fixed with deep+timestamp; notarization pipeline solid.\n- Icon conversion scripted via ictool with docs.\n- Menu: settings submenu, no GitHub item; About link clickable.\n\n## 0.1.1 — 2025-11-16\n- Launch-at-login toggle (SMAppService) and saved preference applied at startup.\n- Sparkle auto-update wiring (SUFeedURL to GitHub, SUPublicEDKey set); Settings submenu with auto-update toggle + Check for Updates.\n- Menu cleanup: settings grouped, GitHub menu removed, About link clickable.\n- Usage parser scans newest session logs until it finds `token_count` events.\n- Icon pipeline fixed: regenerated `.icns` via ictool with proper transparency (docs in docs/icon.md).\n- Added lint/format configs, Swift Testing, strict concurrency, and usage parser tests.\n- Notarized release build \"CodexBar-0.1.0.zip\" remains current artifact; app version 0.1.1.\n\n## 0.1.0 — 2025-11-16\n- Initial CodexBar release: macOS 15+ menu bar app, no Dock icon.\n- Reads latest Codex CLI `token_count` events from session logs (5h + weekly usage, reset times); no extra login or browser scraping.\n- Shows account email/plan decoded locally from `auth.json`.\n- Horizontal dual-bar icon (top = 5h, bottom = weekly); dims on errors.\n- Configurable refresh cadence, manual refresh, and About links.\n- Async off-main log parsing for responsiveness; strict-concurrency build flags enabled.\n- Packaging + signing/notarization scripts (arm64); build scripts convert `.icon` bundle to `.icns`.\n"
  },
  {
    "path": "FORK_STATUS.md",
    "content": "# CodexBar Fork - Current Status\n\n**Last Updated:** January 4, 2026\n**Fork Maintainer:** Brandon Charleson\n**Branch:** `feature/augment-integration`\n\n---\n\n## ✅ Completed Work\n\n### Phase 1: Fork Identity & Credits ✓\n\n**Commits:**\n1. `da3d13e` - \"feat: establish fork identity with dual attribution\"\n2. `745293e` - \"docs: add fork roadmap and quick start guide\"\n3. `8a87473` - \"docs: add fork status tracking document\"\n4. `df75ae2` - \"feat: comprehensive multi-upstream fork management system\"\n\n**Changes:**\n- ✅ Updated About section with dual attribution (original + fork)\n- ✅ Updated PreferencesAboutPane with organized sections\n- ✅ Changed app icon click to open fork repository\n- ✅ Updated README with fork notice and enhancements section\n- ✅ Created comprehensive `docs/augment.md` documentation\n- ✅ Created `docs/FORK_ROADMAP.md` with 5-phase plan\n- ✅ Created `docs/FORK_QUICK_START.md` developer guide\n- ✅ Created `FORK_STATUS.md` tracking document\n- ✅ **Implemented complete multi-upstream management system**\n\n**Build Status:** ✅ App builds and runs successfully\n\n### Multi-Upstream Management System ✓\n\n**Automation Scripts:**\n- ✅ `Scripts/check_upstreams.sh` - Monitor both upstreams\n- ✅ `Scripts/review_upstream.sh` - Create review branches\n- ✅ `Scripts/prepare_upstream_pr.sh` - Prepare upstream PRs\n- ✅ `Scripts/analyze_quotio.sh` - Analyze quotio patterns\n\n**GitHub Actions:**\n- ✅ `.github/workflows/upstream-monitor.yml` - Automated monitoring\n\n**Documentation:**\n- ✅ `docs/UPSTREAM_STRATEGY.md` - Complete management guide\n- ✅ `docs/QUOTIO_ANALYSIS.md` - Pattern analysis framework\n- ✅ `docs/FORK_SETUP.md` - One-time setup guide\n\n---\n\n## 🎯 Current State\n\n### What Works\n- ✅ Fork identity clearly established\n- ✅ Dual attribution in place (original + fork)\n- ✅ Comprehensive documentation\n- ✅ Clear development roadmap\n- ✅ App builds without errors\n- ✅ All existing functionality preserved\n- ✅ **Multi-upstream management system operational**\n- ✅ **Automated upstream monitoring configured**\n- ✅ **Quotio analysis framework ready**\n\n### Critical Discovery\n- ⚠️ **Upstream (steipete) has REMOVED Augment provider**\n  - 627 lines deleted from `AugmentStatusProbe.swift`\n  - 88 lines deleted from `AugmentStatusProbeTests.swift`\n  - **This validates our fork strategy!**\n  - We preserve Augment support for our users\n  - We can selectively sync other improvements\n\n### Known Issues\n- ⚠️ Augment cookie disconnection (Phase 2 will address)\n- ⚠️ Debug print statements in AugmentStatusProbe.swift (needs proper logging)\n\n### Uncommitted Changes\n- `Sources/CodexBarCore/Providers/Augment/AugmentStatusProbe.swift` has debug print statements\n  - These should be replaced with proper `CodexBarLog` logging in Phase 2\n  - Currently unstaged to keep commits clean\n\n---\n\n## 📋 Next Steps\n\n### URGENT: Upstream Sync Decision\n**Before proceeding with Phase 2, decide on upstream sync strategy:**\n\n1. **Review upstream changes:**\n   ```bash\n   ./Scripts/check_upstreams.sh upstream\n   ./Scripts/review_upstream.sh upstream\n   ```\n\n2. **Decide what to sync:**\n   - ✅ Vertex AI improvements (5 commits)\n   - ✅ SwiftFormat/SwiftLint fixes\n   - ❌ Augment provider removal (SKIP!)\n\n3. **Cherry-pick valuable commits:**\n   ```bash\n   git checkout -b upstream-sync/vertex-improvements\n   git cherry-pick 001019c  # style fixes\n   git cherry-pick e4f1e4c  # vertex token cost\n   git cherry-pick 202efde  # vertex fix\n   git cherry-pick 0c2f888  # vertex docs\n   git cherry-pick 3c4ca30  # vertex tracking\n   # Skip Augment removal commits!\n   ```\n\n### Immediate (Phase 2)\n1. **Replace debug prints with proper logging**\n   - Use `CodexBarLog.logger(\"augment\")` pattern\n   - Add structured metadata\n   - Follow Claude/Cursor provider patterns\n\n2. **Enhanced cookie diagnostics**\n   - Log cookie expiration times\n   - Track refresh attempts\n   - Add domain filtering diagnostics\n\n3. **Session keepalive monitoring**\n   - Add keepalive status to debug pane\n   - Log refresh attempts\n   - Add manual \"Force Refresh\" button\n\n### Short Term (Phases 3-4)\n- **Analyze Quotio features** using `./Scripts/analyze_quotio.sh`\n- **Regular upstream monitoring** (automated via GitHub Actions)\n- **Weekly sync routine** (Monday: upstream, Thursday: quotio)\n\n### Medium Term (Phase 5)\n- Implement multi-account management (inspired by quotio)\n- Start with Augment provider\n- Extend to other providers\n\n---\n\n## 📁 Key Files Modified\n\n### Source Code\n- `Sources/CodexBar/About.swift` - Dual attribution\n- `Sources/CodexBar/PreferencesAboutPane.swift` - Organized sections\n- `Sources/CodexBarCore/Providers/Augment/AugmentStatusProbe.swift` - Debug prints (unstaged)\n\n### Documentation\n- `README.md` - Fork notice and enhancements\n- `docs/augment.md` - Augment provider guide (NEW)\n- `docs/FORK_ROADMAP.md` - Development roadmap (NEW)\n- `docs/FORK_QUICK_START.md` - Quick reference (NEW)\n\n---\n\n## 🔄 Git Status\n\n```bash\n# Current branch\nfeature/augment-integration\n\n# Commits ahead of main\n4 commits:\n- da3d13e: Fork identity with dual attribution\n- 745293e: Roadmap and quick start guide\n- 8a87473: Fork status tracking\n- df75ae2: Multi-upstream management system\n\n# Uncommitted changes\nM Sources/CodexBarCore/Providers/Augment/AugmentStatusProbe.swift (debug prints)\n\n# Git remotes configured\norigin    git@github.com:topoffunnel/CodexBar.git\nupstream  https://github.com/steipete/CodexBar.git (needs to be added)\nquotio    https://github.com/nguyenphutrong/quotio.git (needs to be added)\n```\n\n---\n\n## 🚀 How to Continue\n\n### RECOMMENDED: Setup Multi-Upstream System First\n\n```bash\n# 1. Configure git remotes\ngit remote add upstream https://github.com/steipete/CodexBar.git\ngit remote add quotio https://github.com/nguyenphutrong/quotio.git\ngit fetch --all\n\n# 2. Test automation scripts\n./Scripts/check_upstreams.sh\n\n# 3. Review upstream changes (IMPORTANT!)\n./Scripts/review_upstream.sh upstream\n\n# 4. Decide what to sync\n# See \"URGENT: Upstream Sync Decision\" section above\n\n# 5. Analyze quotio\n./Scripts/analyze_quotio.sh\n```\n\n### Option 1: Sync Upstream First, Then Phase 2\n```bash\n# Discard debug prints (will redo in Phase 2)\ngit checkout Sources/CodexBarCore/Providers/Augment/AugmentStatusProbe.swift\n\n# Sync valuable upstream changes\ngit checkout -b upstream-sync/vertex-improvements\n# Cherry-pick commits (see URGENT section)\n\n# Merge to main\ngit checkout main\ngit merge feature/augment-integration\ngit merge upstream-sync/vertex-improvements\n\n# Then start Phase 2\ngit checkout -b feature/augment-diagnostics\n```\n\n### Option 2: Phase 2 First, Sync Later\n```bash\n# Keep debug prints and enhance them\ngit add Sources/CodexBarCore/Providers/Augment/AugmentStatusProbe.swift\n\n# Continue on current branch\n# Replace print() with CodexBarLog.logger(\"augment\")\n# Complete Phase 2\n# Then sync upstream\n```\n\n### Option 3: Merge Current Work, Setup System\n```bash\n# Discard debug prints\ngit checkout Sources/CodexBarCore/Providers/Augment/AugmentStatusProbe.swift\n\n# Merge to main\ngit checkout main\ngit merge feature/augment-integration\n\n# Setup remotes\ngit remote add upstream https://github.com/steipete/CodexBar.git\ngit remote add quotio https://github.com/nguyenphutrong/quotio.git\n\n# Start using the system\n./Scripts/check_upstreams.sh\n```\n\n---\n\n## 📊 Progress Tracking\n\n### Phase 1: Fork Identity ✅ COMPLETE\n- [x] Dual attribution in About\n- [x] Fork notice in README\n- [x] Augment documentation\n- [x] Development roadmap\n- [x] Quick start guide\n\n### Phase 2: Enhanced Diagnostics 🔄 READY TO START\n- [ ] Replace print() with CodexBarLog\n- [ ] Enhanced cookie diagnostics\n- [ ] Session keepalive monitoring\n- [ ] Debug pane improvements\n\n### Phase 3: Quotio Analysis 📋 PLANNED\n- [ ] Feature comparison matrix\n- [ ] Implementation recommendations\n- [ ] Priority ranking\n\n### Phase 4: Upstream Sync 📋 PLANNED\n- [ ] Sync script\n- [ ] Conflict resolution guide\n- [ ] Automated checks\n\n### Phase 5: Multi-Account 📋 PLANNED\n- [ ] Account management UI\n- [ ] Account storage\n- [ ] Account switching\n- [ ] UI enhancements\n\n---\n\n## 🎯 Success Criteria\n\n### Phase 1 (Current) ✅\n- [x] Fork identity clearly established\n- [x] Original author properly credited\n- [x] Comprehensive documentation\n- [x] App builds and runs\n- [x] No regressions\n\n### Phase 2 (Next)\n- [ ] Zero cookie disconnection issues\n- [ ] Proper structured logging\n- [ ] Enhanced debug diagnostics\n- [ ] Manual refresh capability\n- [ ] All tests passing\n\n---\n\n## 📞 Questions & Decisions Needed\n\n### Before Starting Phase 2\n1. **Logging approach:** Keep debug prints and enhance, or start fresh?\n2. **Branch strategy:** Continue on `feature/augment-integration` or create new branch?\n3. **Merge timing:** Merge Phase 1 to main first, or continue with all phases?\n\n### For Phase 3\n1. **Quotio access:** Do you have access to Quotio source code?\n2. **Feature priority:** Which Quotio features are most important?\n3. **Timeline:** How much time to allocate for analysis?\n\n### For Phase 5\n1. **Account limit:** How many accounts per provider?\n2. **UI design:** Menu bar dropdown or separate window?\n3. **Storage:** Keychain per account or shared?\n\n---\n\n## 🔗 Quick Links\n\n- **Roadmap:** `docs/FORK_ROADMAP.md`\n- **Quick Start:** `docs/FORK_QUICK_START.md`\n- **Augment Docs:** `docs/augment.md`\n- **Original Repo:** https://github.com/steipete/CodexBar\n- **Fork Repo:** https://github.com/topoffunnel/CodexBar\n\n---\n\n## 💡 Recommendations\n\n1. **Merge Phase 1 to main** - Establish fork identity as baseline\n2. **Create Phase 2 branch** - `feature/augment-diagnostics`\n3. **Start with logging** - Replace prints with proper CodexBarLog\n4. **Test thoroughly** - Ensure no regressions\n5. **Document as you go** - Update docs with findings\n\n---\n\n**Ready to proceed with Phase 2?** See `docs/FORK_ROADMAP.md` for detailed tasks.\n\n"
  },
  {
    "path": "IMPLEMENTATION_SUMMARY.md",
    "content": "# CodexBar Fork - Implementation Summary\n\n**Date:** January 4, 2026  \n**Implementer:** Augment AI Assistant  \n**For:** Brandon Charleson (topoffunnel.com)\n\n---\n\n## 🎉 What Was Accomplished\n\n### Phase 1: Fork Identity & Credits ✅ COMPLETE\n\n**Objective:** Establish clear fork identity while properly crediting original author\n\n**Deliverables:**\n1. **Dual Attribution System**\n   - Updated `About.swift` with original author + fork maintainer\n   - Updated `PreferencesAboutPane.swift` with organized sections\n   - App icon click now opens fork repository\n   - Clear separation of original vs fork contributions\n\n2. **Documentation Suite**\n   - `docs/augment.md` - Comprehensive Augment provider guide (150+ lines)\n   - `docs/FORK_ROADMAP.md` - 5-phase development plan\n   - `docs/FORK_QUICK_START.md` - Developer quick reference\n   - `FORK_STATUS.md` - Living status tracker\n\n3. **README Updates**\n   - Fork notice at top with link to original\n   - \"Fork Enhancements\" section documenting improvements\n   - Updated credits with dual attribution\n   - Clear differentiation from original\n\n**Result:** Fork has professional identity, ready for distribution via topoffunnel.com\n\n---\n\n### Multi-Upstream Management System ✅ COMPLETE\n\n**Objective:** Monitor and selectively incorporate changes from two upstream repositories\n\n**Deliverables:**\n\n#### 1. Automation Scripts (4 scripts, all executable)\n\n**`Scripts/check_upstreams.sh`**\n- Monitors both upstream and quotio for new commits\n- Shows commit summaries and file changes\n- Color-coded output for easy scanning\n- Usage: `./Scripts/check_upstreams.sh [upstream|quotio|all]`\n\n**`Scripts/review_upstream.sh`**\n- Creates review branch for upstream changes\n- Shows detailed commit log and diffs\n- Generates review log file\n- Usage: `./Scripts/review_upstream.sh [upstream|quotio]`\n\n**`Scripts/prepare_upstream_pr.sh`**\n- Creates clean branch from upstream/main for PR submission\n- Provides guidelines for what to include/exclude\n- Prevents fork branding from going upstream\n- Usage: `./Scripts/prepare_upstream_pr.sh <feature-name>`\n\n**`Scripts/analyze_quotio.sh`**\n- Analyzes quotio repository structure and recent changes\n- Generates analysis report with action items\n- Helps identify patterns to adapt (not copy)\n- Usage: `./Scripts/analyze_quotio.sh [feature-area]`\n\n#### 2. GitHub Actions Workflow\n\n**`.github/workflows/upstream-monitor.yml`**\n- Runs Monday and Thursday at 9 AM UTC\n- Checks both upstreams for new commits\n- Creates/updates GitHub issue with summaries\n- Provides links to review changes\n- Can be triggered manually\n\n#### 3. Comprehensive Documentation (3 guides)\n\n**`docs/UPSTREAM_STRATEGY.md`** (630+ lines)\n- Complete multi-upstream management guide\n- Git repository structure and remote configuration\n- Workflows for monitoring, reviewing, incorporating changes\n- Decision matrix: what to contribute upstream vs keep in fork\n- Commit message strategies and attribution\n- Practical examples and troubleshooting\n- Best practices and success metrics\n\n**`docs/QUOTIO_ANALYSIS.md`** (150+ lines)\n- Framework for learning from quotio patterns\n- Ethical guidelines (adapt patterns, don't copy code)\n- Analysis process and templates\n- Feature comparison matrix\n- Implementation planning\n- Legal and attribution considerations\n\n**`docs/FORK_SETUP.md`** (150+ lines)\n- One-time setup guide for git remotes\n- Script testing and verification\n- Critical discovery documentation\n- Selective sync strategy\n- Regular workflow recommendations\n\n---\n\n## 🚨 Critical Discovery\n\n**Upstream (steipete) has REMOVED the Augment provider!**\n\n**Evidence:**\n```\nFiles changed in upstream:\n .../Providers/Augment/AugmentStatusProbe.swift     | 627 deletions\n Tests/CodexBarTests/AugmentStatusProbeTests.swift  |  88 deletions\n```\n\n**Impact:**\n- ✅ **Validates fork strategy** - We preserve features important to our users\n- ✅ **Justifies independent development** - Can't rely on upstream for Augment\n- ✅ **Enables selective sync** - Cherry-pick valuable changes, skip Augment removal\n- ✅ **Protects user experience** - Fork users keep Augment functionality\n\n**Action Required:**\nWhen syncing with upstream, must cherry-pick commits selectively to avoid losing Augment support.\n\n---\n\n## 📊 Commits Summary\n\n**Total Commits:** 5\n\n1. `da3d13e` - Fork identity with dual attribution\n2. `745293e` - Roadmap and quick start guide\n3. `8a87473` - Fork status tracking document\n4. `df75ae2` - Multi-upstream management system\n5. `158d00c` - Updated fork status\n\n**Lines Added:** ~2,500+ lines of documentation and automation\n**Files Created:** 11 new files\n**Scripts Created:** 4 executable automation scripts\n**Workflows Created:** 1 GitHub Actions workflow\n\n---\n\n## 🎯 Strategic Benefits\n\n### For Fork Development\n1. **Independence** - Can develop features without upstream dependency\n2. **Selective Sync** - Cherry-pick valuable improvements, skip unwanted changes\n3. **Attribution Protection** - Fork-specific commits stay separate\n4. **User Focus** - Preserve features important to your users (Augment)\n\n### For Upstream Relationship\n1. **Contribution Ready** - Clean PR branches for upstream submissions\n2. **Good Citizenship** - Can contribute bug fixes and improvements\n3. **Proper Credit** - Attribution system respects original author\n4. **Flexibility** - Option to contribute or keep changes in fork\n\n### For Learning from Quotio\n1. **Ethical Framework** - Clear guidelines for pattern analysis\n2. **Legal Protection** - Adapt patterns, don't copy code\n3. **Innovation** - Learn from their solutions, implement independently\n4. **Attribution** - Credit inspiration appropriately\n\n---\n\n## 📋 Current State\n\n### What's Ready\n- ✅ Fork identity established\n- ✅ Comprehensive documentation\n- ✅ Automation scripts tested and working\n- ✅ GitHub Actions workflow configured\n- ✅ Git remotes documented (need to be added)\n- ✅ Selective sync strategy defined\n- ✅ App builds and runs successfully\n\n### What's Pending\n- ⏳ Git remotes need to be added (one-time setup)\n- ⏳ Upstream sync decision needed (5 new commits available)\n- ⏳ Quotio analysis to be performed\n- ⏳ Phase 2 (Enhanced Augment diagnostics)\n\n### Known Issues\n- ⚠️ Augment cookie disconnection (Phase 2 will address)\n- ⚠️ Debug print statements in AugmentStatusProbe.swift (unstaged)\n\n---\n\n## 🚀 Next Steps for You\n\n### Immediate (Before Phase 2)\n\n**1. Setup Git Remotes**\n```bash\ngit remote add upstream https://github.com/steipete/CodexBar.git\ngit remote add quotio https://github.com/nguyenphutrong/quotio.git\ngit fetch --all\n```\n\n**2. Test Automation**\n```bash\n./Scripts/check_upstreams.sh\n./Scripts/review_upstream.sh upstream\n./Scripts/analyze_quotio.sh\n```\n\n**3. Decide on Upstream Sync**\n- Review 5 new upstream commits\n- Cherry-pick valuable changes (Vertex AI improvements)\n- Skip Augment removal commits\n- See `FORK_STATUS.md` for detailed instructions\n\n### Short Term (This Week)\n\n**4. Merge to Main**\n```bash\ngit checkout main\ngit merge feature/augment-integration\n```\n\n**5. Enable GitHub Actions**\n- Push to your fork\n- Enable Actions in repository settings\n- Verify workflow runs\n\n**6. Start Regular Monitoring**\n- Monday: Check upstream (`./Scripts/check_upstreams.sh upstream`)\n- Thursday: Analyze quotio (`./Scripts/analyze_quotio.sh`)\n\n### Medium Term (Next 2 Weeks)\n\n**7. Complete Phase 2**\n- Enhanced Augment diagnostics\n- Proper logging with CodexBarLog\n- Session keepalive monitoring\n\n**8. Quotio Analysis**\n- Document multi-account patterns\n- Plan implementation\n- Prioritize features\n\n---\n\n## 📖 Documentation Index\n\n### Core Documents\n- `README.md` - Main documentation with fork notice\n- `FORK_STATUS.md` - Current status and next steps\n- `IMPLEMENTATION_SUMMARY.md` - This document\n\n### Setup & Strategy\n- `docs/FORK_SETUP.md` - One-time setup guide\n- `docs/FORK_QUICK_START.md` - Developer quick reference\n- `docs/UPSTREAM_STRATEGY.md` - Multi-upstream management\n- `docs/FORK_ROADMAP.md` - 5-phase development plan\n\n### Provider & Analysis\n- `docs/augment.md` - Augment provider guide\n- `docs/QUOTIO_ANALYSIS.md` - Quotio pattern analysis framework\n\n### Scripts\n- `Scripts/check_upstreams.sh` - Monitor upstreams\n- `Scripts/review_upstream.sh` - Review changes\n- `Scripts/prepare_upstream_pr.sh` - Prepare PRs\n- `Scripts/analyze_quotio.sh` - Analyze quotio\n\n---\n\n## 💡 Key Insights\n\n1. **Fork Validation** - Upstream removing Augment proves fork was necessary\n2. **Best of Both Worlds** - Can learn from two sources while maintaining independence\n3. **Selective Sync** - Cherry-picking gives control over what changes to adopt\n4. **Attribution Matters** - Separate commits protect your contributions\n5. **Automation Wins** - Scripts and workflows reduce manual effort\n\n---\n\n## ✅ Success Criteria Met\n\n- [x] Fork identity clearly established\n- [x] Original author properly credited\n- [x] Comprehensive documentation\n- [x] Multi-upstream monitoring system\n- [x] Automation scripts working\n- [x] GitHub Actions configured\n- [x] Selective sync strategy defined\n- [x] App builds and runs\n- [x] No regressions\n\n---\n\n**Status:** Phase 1 COMPLETE + Multi-Upstream System OPERATIONAL  \n**Ready for:** Upstream sync decision + Phase 2 development  \n**Recommendation:** Setup remotes, sync upstream, then proceed to Phase 2\n\n"
  },
  {
    "path": "Icon.icon/icon.json",
    "content": "{\n  \"fill\" : {\n    \"automatic-gradient\" : \"extended-srgb:0.00000,0.53333,1.00000,1.00000\"\n  },\n  \"groups\" : [\n    {\n      \"layers\" : [\n        {\n          \"image-name\" : \"codexbar.png\",\n          \"name\" : \"codexbar\",\n          \"position\" : {\n            \"scale\" : 1.4,\n            \"translation-in-points\" : [\n              0,\n              0\n            ]\n          }\n        }\n      ],\n      \"shadow\" : {\n        \"kind\" : \"neutral\",\n        \"opacity\" : 0.5\n      },\n      \"translucency\" : {\n        \"enabled\" : true,\n        \"value\" : 0.5\n      }\n    }\n  ],\n  \"supported-platforms\" : {\n    \"circles\" : [\n      \"watchOS\"\n    ],\n    \"squares\" : \"shared\"\n  }\n}"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2026 Peter Steinberger\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Package.resolved",
    "content": "{\n  \"originHash\" : \"74bd6f3ab6e0b0cb0c2cddb00f2167c2ab0a1c00cd54ffc1a2899c7ef8c56367\",\n  \"pins\" : [\n    {\n      \"identity\" : \"commander\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/steipete/Commander\",\n      \"state\" : {\n        \"revision\" : \"9e349575c8e3c6745e81fe19e5bb5efa01b078ce\",\n        \"version\" : \"0.2.1\"\n      }\n    },\n    {\n      \"identity\" : \"keyboardshortcuts\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/sindresorhus/KeyboardShortcuts\",\n      \"state\" : {\n        \"revision\" : \"1aef85578fdd4f9eaeeb8d53b7b4fc31bf08fe27\",\n        \"version\" : \"2.4.0\"\n      }\n    },\n    {\n      \"identity\" : \"sparkle\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/sparkle-project/Sparkle\",\n      \"state\" : {\n        \"revision\" : \"5581748cef2bae787496fe6d61139aebe0a451f6\",\n        \"version\" : \"2.8.1\"\n      }\n    },\n    {\n      \"identity\" : \"sweetcookiekit\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/steipete/SweetCookieKit\",\n      \"state\" : {\n        \"revision\" : \"4d5b71ffbb296937dc5ee8472f64721bca771cf0\",\n        \"version\" : \"0.4.0\"\n      }\n    },\n    {\n      \"identity\" : \"swift-log\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/apple/swift-log\",\n      \"state\" : {\n        \"revision\" : \"2778fd4e5a12a8aaa30a3ee8285f4ce54c5f3181\",\n        \"version\" : \"1.9.1\"\n      }\n    },\n    {\n      \"identity\" : \"swift-syntax\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/apple/swift-syntax\",\n      \"state\" : {\n        \"revision\" : \"0687f71944021d616d34d922343dcef086855920\",\n        \"version\" : \"600.0.1\"\n      }\n    }\n  ],\n  \"version\" : 3\n}\n"
  },
  {
    "path": "Package.swift",
    "content": "// swift-tools-version: 6.2\nimport CompilerPluginSupport\nimport Foundation\nimport PackageDescription\n\nlet sweetCookieKitPath = \"../SweetCookieKit\"\nlet useLocalSweetCookieKit =\n    ProcessInfo.processInfo.environment[\"CODEXBAR_USE_LOCAL_SWEETCOOKIEKIT\"] == \"1\"\nlet sweetCookieKitDependency: Package.Dependency =\n    useLocalSweetCookieKit && FileManager.default.fileExists(atPath: sweetCookieKitPath)\n    ? .package(path: sweetCookieKitPath)\n    : .package(url: \"https://github.com/steipete/SweetCookieKit\", from: \"0.4.0\")\n\nlet package = Package(\n    name: \"CodexBar\",\n    platforms: [\n        .macOS(.v14),\n    ],\n    dependencies: [\n        .package(url: \"https://github.com/sparkle-project/Sparkle\", from: \"2.8.1\"),\n        .package(url: \"https://github.com/steipete/Commander\", from: \"0.2.1\"),\n        .package(url: \"https://github.com/apple/swift-log\", from: \"1.9.1\"),\n        .package(url: \"https://github.com/apple/swift-syntax\", from: \"600.0.1\"),\n        .package(url: \"https://github.com/sindresorhus/KeyboardShortcuts\", from: \"2.4.0\"),\n        sweetCookieKitDependency,\n    ],\n    targets: {\n        var targets: [Target] = [\n            .target(\n                name: \"CodexBarCore\",\n                dependencies: [\n                    \"CodexBarMacroSupport\",\n                    .product(name: \"Logging\", package: \"swift-log\"),\n                    .product(name: \"SweetCookieKit\", package: \"SweetCookieKit\"),\n                ],\n                swiftSettings: [\n                    .enableUpcomingFeature(\"StrictConcurrency\"),\n                ]),\n            .macro(\n                name: \"CodexBarMacros\",\n                dependencies: [\n                    .product(name: \"SwiftCompilerPlugin\", package: \"swift-syntax\"),\n                    .product(name: \"SwiftSyntaxBuilder\", package: \"swift-syntax\"),\n                    .product(name: \"SwiftSyntaxMacros\", package: \"swift-syntax\"),\n                ]),\n            .target(\n                name: \"CodexBarMacroSupport\",\n                dependencies: [\n                    \"CodexBarMacros\",\n                ]),\n            .executableTarget(\n                name: \"CodexBarCLI\",\n                dependencies: [\n                    \"CodexBarCore\",\n                    .product(name: \"Commander\", package: \"Commander\"),\n                ],\n                path: \"Sources/CodexBarCLI\",\n                swiftSettings: [\n                    .enableUpcomingFeature(\"StrictConcurrency\"),\n                ]),\n            .testTarget(\n                name: \"CodexBarLinuxTests\",\n                dependencies: [\"CodexBarCore\", \"CodexBarCLI\"],\n                path: \"TestsLinux\",\n                swiftSettings: [\n                    .enableUpcomingFeature(\"StrictConcurrency\"),\n                    .enableExperimentalFeature(\"SwiftTesting\"),\n                ]),\n        ]\n\n        #if os(macOS)\n        targets.append(contentsOf: [\n            .executableTarget(\n                name: \"CodexBarClaudeWatchdog\",\n                dependencies: [],\n                path: \"Sources/CodexBarClaudeWatchdog\",\n                swiftSettings: [\n                    .enableUpcomingFeature(\"StrictConcurrency\"),\n                ]),\n            .executableTarget(\n                name: \"CodexBar\",\n                dependencies: [\n                    .product(name: \"Sparkle\", package: \"Sparkle\"),\n                    .product(name: \"KeyboardShortcuts\", package: \"KeyboardShortcuts\"),\n                    \"CodexBarMacroSupport\",\n                    \"CodexBarCore\",\n                ],\n                path: \"Sources/CodexBar\",\n                resources: [\n                    .process(\"Resources\"),\n                ],\n                swiftSettings: [\n                    // Opt into Swift 6 strict concurrency (approachable migration path).\n                    .enableUpcomingFeature(\"StrictConcurrency\"),\n                    .define(\"ENABLE_SPARKLE\"),\n                ]),\n            .executableTarget(\n                name: \"CodexBarWidget\",\n                dependencies: [\"CodexBarCore\"],\n                path: \"Sources/CodexBarWidget\",\n                swiftSettings: [\n                    .enableUpcomingFeature(\"StrictConcurrency\"),\n                ]),\n            .executableTarget(\n                name: \"CodexBarClaudeWebProbe\",\n                dependencies: [\"CodexBarCore\"],\n                path: \"Sources/CodexBarClaudeWebProbe\",\n                swiftSettings: [\n                    .enableUpcomingFeature(\"StrictConcurrency\"),\n                ]),\n        ])\n\n        targets.append(.testTarget(\n            name: \"CodexBarTests\",\n            dependencies: [\"CodexBar\", \"CodexBarCore\", \"CodexBarCLI\", \"CodexBarWidget\"],\n            path: \"Tests\",\n            swiftSettings: [\n                .enableUpcomingFeature(\"StrictConcurrency\"),\n                .enableExperimentalFeature(\"SwiftTesting\"),\n            ]))\n        #endif\n\n        return targets\n    }())\n"
  },
  {
    "path": "README.md",
    "content": "# CodexBar 🎚️ - May your tokens never run out.\n\nTiny macOS 14+ menu bar app that keeps your Codex, Claude, Cursor, Gemini, Antigravity, Droid (Factory), Copilot, z.ai, Kiro, Vertex AI, Augment, Amp, JetBrains AI, and OpenRouter limits visible (session + weekly where available) and shows when each window resets. One status item per provider (or Merge Icons mode with a provider switcher and optional Overview tab); enable what you use from Settings. No Dock icon, minimal UI, dynamic bar icons in the menu bar.\n\n<img src=\"codexbar.png\" alt=\"CodexBar menu screenshot\" width=\"520\" />\n\n## Install\n\n### Requirements\n- macOS 14+ (Sonoma)\n\n### GitHub Releases\nDownload: <https://github.com/steipete/CodexBar/releases>\n\n### Homebrew\n```bash\nbrew install --cask steipete/tap/codexbar\n```\n\n### Linux (CLI only)\n```bash\nbrew install steipete/tap/codexbar\n```\nOr download `CodexBarCLI-v<tag>-linux-<arch>.tar.gz` from GitHub Releases.\nLinux support via Omarchy: community Waybar module and TUI, driven by the `codexbar` executable.\n\n### First run\n- Open Settings → Providers and enable what you use.\n- Install/sign in to the provider sources you rely on (e.g. `codex`, `claude`, `gemini`, browser cookies, or OAuth; Antigravity requires the Antigravity app running).\n- Optional: Settings → Providers → Codex → OpenAI cookies (Automatic or Manual) to add dashboard extras.\n\n## Providers\n\n- [Codex](docs/codex.md) — Local Codex CLI RPC (+ PTY fallback) and optional OpenAI web dashboard extras.\n- [Claude](docs/claude.md) — OAuth API or browser cookies (+ CLI PTY fallback); session + weekly usage.\n- [Cursor](docs/cursor.md) — Browser session cookies for plan + usage + billing resets.\n- [Gemini](docs/gemini.md) — OAuth-backed quota API using Gemini CLI credentials (no browser cookies).\n- [Antigravity](docs/antigravity.md) — Local language server probe (experimental); no external auth.\n- [Droid](docs/factory.md) — Browser cookies + WorkOS token flows for Factory usage + billing.\n- [Copilot](docs/copilot.md) — GitHub device flow + Copilot internal usage API.\n- [z.ai](docs/zai.md) — API token (Keychain) for quota + MCP windows.\n- [Kimi](docs/kimi.md) — Auth token (JWT from `kimi-auth` cookie) for weekly quota + 5‑hour rate limit.\n- [Kimi K2](docs/kimi-k2.md) — API key for credit-based usage totals.\n- [Kiro](docs/kiro.md) — CLI-based usage via `kiro-cli /usage` command; monthly credits + bonus credits.\n- [Vertex AI](docs/vertexai.md) — Google Cloud gcloud OAuth with token cost tracking from local Claude logs.\n- [Augment](docs/augment.md) — Browser cookie-based authentication with automatic session keepalive; credits tracking and usage monitoring.\n- [Amp](docs/amp.md) — Browser cookie-based authentication with Amp Free usage tracking.\n- [JetBrains AI](docs/jetbrains.md) — Local XML-based quota from JetBrains IDE configuration; monthly credits tracking.\n- [OpenRouter](docs/openrouter.md) — API token for credit-based usage tracking across multiple AI providers.\n- Open to new providers: [provider authoring guide](docs/provider.md).\n\n## Icon & Screenshot\nThe menu bar icon is a tiny two-bar meter:\n- Top bar: 5‑hour/session window. If weekly is missing/exhausted and credits are available, it becomes a thicker credits bar.\n- Bottom bar: weekly window (hairline).\n- Errors/stale data dim the icon; status overlays indicate incidents.\n\n## Features\n- Multi-provider menu bar with per-provider toggles (Settings → Providers).\n- Session + weekly meters with reset countdowns.\n- Optional Codex web dashboard enrichments (code review remaining, usage breakdown, credits history).\n- Local cost-usage scan for Codex + Claude (last 30 days).\n- Provider status polling with incident badges in the menu and icon overlay.\n- Merge Icons mode to combine providers into one status item + switcher, with an optional Overview tab for up to three providers.\n- Refresh cadence presets (manual, 1m, 2m, 5m, 15m).\n- Bundled CLI (`codexbar`) for scripts and CI (including `codexbar cost --provider codex|claude` for local cost usage); Linux CLI builds available.\n- WidgetKit widget mirrors the menu card snapshot.\n- Privacy-first: on-device parsing by default; browser cookies are opt-in and reused (no passwords stored).\n\n## Privacy note\nWondering if CodexBar scans your disk? It doesn’t crawl your filesystem; it reads a small set of known locations (browser cookies/local storage, local JSONL logs) when the related features are enabled. See the discussion and audit notes in [issue #12](https://github.com/steipete/CodexBar/issues/12).\n\n## macOS permissions (why they’re needed)\n- **Full Disk Access (optional)**: only required to read Safari cookies/local storage for web-based providers (Codex web, Claude web, Cursor, Droid/Factory). If you don’t grant it, use Chrome/Firefox cookies or CLI-only sources instead.\n- **Keychain access (prompted by macOS)**:\n  - Chrome cookie import needs the “Chrome Safe Storage” key to decrypt cookies.\n  - Claude OAuth credentials (written by the Claude CLI) are read from Keychain when present.\n  - z.ai API token is stored in Keychain from Preferences → Providers; Copilot stores its API token in Keychain during device flow.\n  - **How do I prevent those keychain alerts?**\n    - Open **Keychain Access.app** → login keychain → search the item (e.g., “Claude Code-credentials”).\n    - Open the item → **Access Control** → add `CodexBar.app` under “Always allow access by these applications”.\n    - Prefer adding just CodexBar (avoid “Allow all applications” unless you want it wide open).\n    - Relaunch CodexBar after saving.\n    - Reference screenshot: ![Keychain access control](docs/keychain-allow.png)\n  - **How to do the same for the browser?**\n    - Find the browser’s “Safe Storage” key (e.g., “Chrome Safe Storage”, “Brave Safe Storage”, “Firefox”, “Microsoft Edge Safe Storage”).\n    - Open the item → **Access Control** → add `CodexBar.app` under “Always allow access by these applications”.\n    - This removes the prompt when CodexBar decrypts cookies for that browser.\n- **Files & Folders prompts (folder/volume access)**: CodexBar launches provider CLIs (codex/claude/gemini/antigravity). If those CLIs read a project directory or external drive, macOS may ask CodexBar for that folder/volume (e.g., Desktop or an external volume). This is driven by the CLI’s working directory, not background disk scanning.\n- **What we do not request**: no Screen Recording, Accessibility, or Automation permissions; no passwords are stored (browser cookies are reused when you opt in).\n\n## Docs\n- Providers overview: [docs/providers.md](docs/providers.md)\n- Provider authoring: [docs/provider.md](docs/provider.md)\n- UI & icon notes: [docs/ui.md](docs/ui.md)\n- CLI reference: [docs/cli.md](docs/cli.md)\n- Architecture: [docs/architecture.md](docs/architecture.md)\n- Refresh loop: [docs/refresh-loop.md](docs/refresh-loop.md)\n- Status polling: [docs/status.md](docs/status.md)\n- Sparkle updates: [docs/sparkle.md](docs/sparkle.md)\n- Release checklist: [docs/RELEASING.md](docs/RELEASING.md)\n\n## Getting started (dev)\n- Clone the repo and open it in Xcode or run the scripts directly.\n- Launch once, then toggle providers in Settings → Providers.\n- Install/sign in to provider sources you rely on (CLIs, browser cookies, or OAuth).\n- Optional: set OpenAI cookies (Automatic or Manual) for Codex dashboard extras.\n\n## Build from source\n```bash\nswift build -c release          # or debug for development\n./Scripts/package_app.sh        # builds CodexBar.app in-place\nCODEXBAR_SIGNING=adhoc ./Scripts/package_app.sh  # ad-hoc signing (no Apple Developer account)\nopen CodexBar.app\n```\n\nDev loop:\n```bash\n./Scripts/compile_and_run.sh\n```\n\n## Related\n- ✂️ [Trimmy](https://github.com/steipete/Trimmy) — “Paste once, run once.” Flatten multi-line shell snippets so they paste and run.\n- 🧳 [MCPorter](https://mcporter.dev) — TypeScript toolkit + CLI for Model Context Protocol servers.\n- 🧿 [oracle](https://askoracle.dev) — Ask the oracle when you're stuck. Invoke GPT-5 Pro with a custom context and files.\n\n## Looking for a Windows version?\n- [Win-CodexBar](https://github.com/Finesssee/Win-CodexBar)\n\n## Credits\nInspired by [ccusage](https://github.com/ryoppippi/ccusage) (MIT), specifically the cost usage tracking.\n\n## License\nMIT • Peter Steinberger ([steipete](https://twitter.com/steipete))\n"
  },
  {
    "path": "Scripts/analyze_quotio.sh",
    "content": "#!/bin/bash\n# Analyze quotio repository for interesting patterns and features\n# Usage: ./Scripts/analyze_quotio.sh [feature-area]\n\nset -e\n\nAREA=${1:-all}\n\n# Colors\nRED='\\033[0;31m'\nGREEN='\\033[0;32m'\nYELLOW='\\033[1;33m'\nBLUE='\\033[0;34m'\nNC='\\033[0m'\n\necho -e \"${BLUE}==> Fetching latest quotio...${NC}\"\ngit fetch quotio 2>/dev/null || {\n    echo -e \"${YELLOW}Adding quotio remote...${NC}\"\n    git remote add quotio https://github.com/nguyenphutrong/quotio.git\n    git fetch quotio\n}\n\necho \"\"\necho -e \"${GREEN}==> Quotio Repository Analysis${NC}\"\necho \"\"\n\n# Show recent activity\necho -e \"${BLUE}Recent Activity (last 30 days):${NC}\"\ngit log --oneline --graph --remotes=quotio/main --since=\"30 days ago\" | head -20\necho \"\"\n\n# Analyze file structure\necho -e \"${BLUE}File Structure:${NC}\"\ngit ls-tree -r --name-only quotio/main | grep -E '\\.(swift|md)$' | head -30\necho \"\"\n\n# Find interesting patterns based on area\ncase $AREA in\n    \"providers\"|\"all\")\n        echo -e \"${BLUE}Provider Implementations:${NC}\"\n        git ls-tree -r --name-only quotio/main | grep -i provider | head -20\n        echo \"\"\n        ;;\nesac\n\ncase $AREA in\n    \"ui\"|\"all\")\n        echo -e \"${BLUE}UI Components:${NC}\"\n        git ls-tree -r --name-only quotio/main | grep -iE '(view|ui|menu)' | head -20\n        echo \"\"\n        ;;\nesac\n\ncase $AREA in\n    \"auth\"|\"all\")\n        echo -e \"${BLUE}Authentication/Session:${NC}\"\n        git ls-tree -r --name-only quotio/main | grep -iE '(auth|session|cookie|login)' | head -20\n        echo \"\"\n        ;;\nesac\n\n# Show commit messages for pattern analysis\necho -e \"${BLUE}Recent Commit Messages (for pattern analysis):${NC}\"\ngit log --oneline quotio/main --since=\"60 days ago\" | head -30\necho \"\"\n\n# Create analysis report\nREPORT_FILE=\"quotio-analysis-$(date +%Y%m%d).md\"\ncat > \"$REPORT_FILE\" << EOF\n# Quotio Analysis Report\n**Date:** $(date +%Y-%m-%d)\n**Purpose:** Identify patterns and features for CodexBar fork inspiration\n\n## Recent Activity\n\\`\\`\\`\n$(git log --oneline --graph --remotes=quotio/main --since=\"30 days ago\" | head -20)\n\\`\\`\\`\n\n## File Structure\n\\`\\`\\`\n$(git ls-tree -r --name-only quotio/main | grep -E '\\.(swift|md)$' | head -50)\n\\`\\`\\`\n\n## Recent Commits\n\\`\\`\\`\n$(git log --oneline quotio/main --since=\"60 days ago\" | head -30)\n\\`\\`\\`\n\n## Areas of Interest\n\n### Providers\n- [ ] Review provider implementations\n- [ ] Compare with CodexBar approach\n- [ ] Identify improvements\n\n### UI/UX\n- [ ] Menu bar organization\n- [ ] Settings layout\n- [ ] Status indicators\n\n### Authentication\n- [ ] Session management\n- [ ] Cookie handling\n- [ ] OAuth flows\n\n### Multi-Account\n- [ ] Account switching\n- [ ] Account storage\n- [ ] UI patterns\n\n## Action Items\n- [ ] Review specific files of interest\n- [ ] Document patterns (not code)\n- [ ] Create implementation plan\n- [ ] Implement independently\n\n## Notes\nRemember: We're looking for PATTERNS and IDEAS, not copying code.\nAll implementations must be original and follow CodexBar conventions.\nEOF\n\necho -e \"${GREEN}Analysis report saved to: $REPORT_FILE${NC}\"\necho \"\"\necho -e \"${YELLOW}Next steps:${NC}\"\necho \"\"\necho \"1. View specific files:\"\necho \"   ${GREEN}git show quotio/main:path/to/file${NC}\"\necho \"\"\necho \"2. Compare implementations:\"\necho \"   ${GREEN}git diff main quotio/main -- path/to/similar/file${NC}\"\necho \"\"\necho \"3. Review commit details:\"\necho \"   ${GREEN}git log -p quotio/main --since='30 days ago'${NC}\"\necho \"\"\necho \"4. Document patterns in:\"\necho \"   ${GREEN}docs/QUOTIO_ANALYSIS.md${NC}\"\necho \"\"\necho -e \"${BLUE}Remember: Adapt patterns, don't copy code!${NC}\"\n\n"
  },
  {
    "path": "Scripts/build_icon.sh",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\nICON_FILE=${1:-Icon.icon}\nBASENAME=${2:-Icon}\nOUT_ROOT=${3:-build/icon}\nXCODE_APP=${XCODE_APP:-/Applications/Xcode.app}\n\nICTOOL=\"$XCODE_APP/Contents/Applications/Icon Composer.app/Contents/Executables/ictool\"\nif [[ ! -x \"$ICTOOL\" ]]; then\n  ICTOOL=\"$XCODE_APP/Contents/Applications/Icon Composer.app/Contents/Executables/icontool\"\nfi\nif [[ ! -x \"$ICTOOL\" ]]; then\n  echo \"ictool/icontool not found. Set XCODE_APP if Xcode is elsewhere.\" >&2\n  exit 1\nfi\n\nICONSET_DIR=\"$OUT_ROOT/${BASENAME}.iconset\"\nTMP_DIR=\"$OUT_ROOT/tmp\"\nmkdir -p \"$ICONSET_DIR\" \"$TMP_DIR\"\n\nMASTER_ART=\"$TMP_DIR/icon_art_824.png\"\nMASTER_1024=\"$TMP_DIR/icon_1024.png\"\n\n# Render inner art (no margin) with macOS Default appearance\n\"$ICTOOL\" \"$ICON_FILE\" \\\n  --export-preview macOS Default 824 824 1 -45 \"$MASTER_ART\"\n\n# Pad to 1024x1024 with transparent border\nsips --padToHeightWidth 1024 1024 \"$MASTER_ART\" --out \"$MASTER_1024\" >/dev/null\n\n# Generate required sizes\nsizes=(16 32 64 128 256 512 1024)\nfor sz in \"${sizes[@]}\"; do\n  out=\"$ICONSET_DIR/icon_${sz}x${sz}.png\"\n  sips -z \"$sz\" \"$sz\" \"$MASTER_1024\" --out \"$out\" >/dev/null\n  if [[ \"$sz\" -ne 1024 ]]; then\n    dbl=$((sz*2))\n    out2=\"$ICONSET_DIR/icon_${sz}x${sz}@2x.png\"\n    sips -z \"$dbl\" \"$dbl\" \"$MASTER_1024\" --out \"$out2\" >/dev/null\n  fi\ndone\n\n# 512x512@2x already covered by 1024; ensure it exists\ncp \"$MASTER_1024\" \"$ICONSET_DIR/icon_512x512@2x.png\"\n\niconutil -c icns \"$ICONSET_DIR\" -o Icon.icns\n\necho \"Icon.icns generated at $(pwd)/Icon.icns\"\n"
  },
  {
    "path": "Scripts/changelog-to-html.sh",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\nVERSION=${1:-}\nCHANGELOG_FILE=${2:-}\n\nif [[ -z \"$VERSION\" ]]; then\n  echo \"Usage: $0 <version> [changelog_file]\" >&2\n  exit 1\nfi\n\nSCRIPT_DIR=$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\nif [[ -z \"$CHANGELOG_FILE\" ]]; then\n  if [[ -f \"$SCRIPT_DIR/../CHANGELOG.md\" ]]; then\n    CHANGELOG_FILE=\"$SCRIPT_DIR/../CHANGELOG.md\"\n  elif [[ -f \"CHANGELOG.md\" ]]; then\n    CHANGELOG_FILE=\"CHANGELOG.md\"\n  elif [[ -f \"../CHANGELOG.md\" ]]; then\n    CHANGELOG_FILE=\"../CHANGELOG.md\"\n  else\n    echo \"Error: Could not find CHANGELOG.md\" >&2\n    exit 1\n  fi\nfi\n\nif [[ ! -f \"$CHANGELOG_FILE\" ]]; then\n  echo \"Error: Changelog file '$CHANGELOG_FILE' not found\" >&2\n  exit 1\nfi\n\nextract_version_section() {\n  local version=$1\n  local file=$2\n  awk -v version=\"$version\" '\n    BEGIN { found=0 }\n    /^## / {\n      if ($0 ~ \"^##[[:space:]]+\" version \"([[:space:]].*|$)\") { found=1; next }\n      if (found) { exit }\n    }\n    found { print }\n  ' \"$file\"\n}\n\nmarkdown_to_html() {\n  local text=$1\n  text=$(echo \"$text\" | sed 's/^### \\(.*\\)$/<h3>\\1<\\/h3>/')\n  text=$(echo \"$text\" | sed 's/^## \\(.*\\)$/<h2>\\1<\\/h2>/')\n  text=$(echo \"$text\" | sed 's/^- \\*\\*\\([^*]*\\)\\*\\*\\(.*\\)$/<li><strong>\\1<\\/strong>\\2<\\/li>/')\n  text=$(echo \"$text\" | sed 's/^- \\([^*].*\\)$/<li>\\1<\\/li>/')\n  text=$(echo \"$text\" | sed 's/\\*\\*\\([^*]*\\)\\*\\*/<strong>\\1<\\/strong>/g')\n  text=$(echo \"$text\" | sed 's/`\\([^`]*\\)`/<code>\\1<\\/code>/g')\n  text=$(echo \"$text\" | sed 's/\\[\\([^]]*\\)\\](\\([^)]*\\))/<a href=\"\\2\">\\1<\\/a>/g')\n  echo \"$text\"\n}\n\nversion_content=$(extract_version_section \"$VERSION\" \"$CHANGELOG_FILE\")\nif [[ -z \"$version_content\" ]]; then\n  echo \"<h2>CodexBar $VERSION</h2>\"\n  echo \"<p>Latest CodexBar update.</p>\"\n  echo \"<p><a href=\\\"https://github.com/steipete/CodexBar/blob/main/CHANGELOG.md\\\">View full changelog</a></p>\"\n  exit 0\nfi\n\necho \"<h2>CodexBar $VERSION</h2>\"\n\nin_list=false\nwhile IFS= read -r line; do\n  if [[ \"$line\" =~ ^- ]]; then\n    if [[ \"$in_list\" == false ]]; then\n      echo \"<ul>\"\n      in_list=true\n    fi\n    markdown_to_html \"$line\"\n  else\n    if [[ \"$in_list\" == true ]]; then\n      echo \"</ul>\"\n      in_list=false\n    fi\n    if [[ -n \"$line\" ]]; then\n      markdown_to_html \"$line\"\n    fi\n  fi\ndone <<< \"$version_content\"\n\nif [[ \"$in_list\" == true ]]; then\n  echo \"</ul>\"\nfi\n\necho \"<p><a href=\\\"https://github.com/steipete/CodexBar/blob/main/CHANGELOG.md\\\">View full changelog</a></p>\"\n"
  },
  {
    "path": "Scripts/check-release-assets.sh",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\nROOT=$(cd \"$(dirname \"$0\")/..\" && pwd)\nsource \"$HOME/Projects/agent-scripts/release/sparkle_lib.sh\"\n\nTAG=${1:-$(git describe --tags --abbrev=0)}\nARTIFACT_PREFIX=\"CodexBar-\"\n\ncheck_assets \"$TAG\" \"$ARTIFACT_PREFIX\"\n"
  },
  {
    "path": "Scripts/check_upstreams.sh",
    "content": "#!/bin/bash\n# Check for new changes in upstream repositories\n# Usage: ./Scripts/check_upstreams.sh [upstream|quotio|all]\n\nset -e\n\nTARGET=${1:-all}\nDAYS=${2:-7}\n\n# Colors for output\nRED='\\033[0;31m'\nGREEN='\\033[0;32m'\nYELLOW='\\033[1;33m'\nBLUE='\\033[0;34m'\nNC='\\033[0m' # No Color\n\necho -e \"${BLUE}==> Fetching upstream changes...${NC}\"\nif [ \"$TARGET\" = \"all\" ] || [ \"$TARGET\" = \"upstream\" ]; then\n    git fetch upstream 2>/dev/null || {\n        echo -e \"${YELLOW}Adding upstream remote...${NC}\"\n        git remote add upstream https://github.com/steipete/CodexBar.git\n        git fetch upstream\n    }\nfi\n\nif [ \"$TARGET\" = \"all\" ] || [ \"$TARGET\" = \"quotio\" ]; then\n    git fetch quotio 2>/dev/null || {\n        echo -e \"${YELLOW}Adding quotio remote...${NC}\"\n        git remote add quotio https://github.com/nguyenphutrong/quotio.git\n        git fetch quotio\n    }\nfi\n\necho \"\"\n\n# Check upstream (steipete)\nif [ \"$TARGET\" = \"all\" ] || [ \"$TARGET\" = \"upstream\" ]; then\n    echo -e \"${BLUE}==> Upstream (steipete/CodexBar) changes:${NC}\"\n    \n    UPSTREAM_COUNT=$(git log --oneline main..upstream/main --no-merges 2>/dev/null | wc -l | tr -d ' ')\n    \n    if [ \"$UPSTREAM_COUNT\" -gt 0 ]; then\n        echo -e \"${GREEN}Found $UPSTREAM_COUNT new commits${NC}\"\n        echo \"\"\n        git log --oneline --graph main..upstream/main --no-merges | head -20\n        echo \"\"\n        echo -e \"${YELLOW}Files changed:${NC}\"\n        git diff --stat main..upstream/main | tail -20\n    else\n        echo -e \"${GREEN}No new commits (up to date)${NC}\"\n    fi\n    echo \"\"\nfi\n\n# Check quotio\nif [ \"$TARGET\" = \"all\" ] || [ \"$TARGET\" = \"quotio\" ]; then\n    echo -e \"${BLUE}==> Quotio changes (last $DAYS days):${NC}\"\n    \n    QUOTIO_COUNT=$(git log --oneline --all --remotes=quotio/main --since=\"$DAYS days ago\" 2>/dev/null | wc -l | tr -d ' ')\n    \n    if [ \"$QUOTIO_COUNT\" -gt 0 ]; then\n        echo -e \"${GREEN}Found $QUOTIO_COUNT commits in last $DAYS days${NC}\"\n        echo \"\"\n        git log --oneline --graph --remotes=quotio/main --since=\"$DAYS days ago\" | head -20\n        echo \"\"\n        echo -e \"${YELLOW}Recent file changes:${NC}\"\n        # Show changes from last 10 commits\n        git diff --stat quotio/main~10..quotio/main 2>/dev/null | tail -20 || echo \"Unable to show diff\"\n    else\n        echo -e \"${GREEN}No new commits in last $DAYS days${NC}\"\n    fi\n    echo \"\"\nfi\n\n# Summary\necho -e \"${BLUE}==> Summary${NC}\"\nif [ \"$TARGET\" = \"all\" ] || [ \"$TARGET\" = \"upstream\" ]; then\n    echo \"Upstream commits: $UPSTREAM_COUNT\"\nfi\nif [ \"$TARGET\" = \"all\" ] || [ \"$TARGET\" = \"quotio\" ]; then\n    echo \"Quotio commits (${DAYS}d): $QUOTIO_COUNT\"\nfi\n\necho \"\"\necho -e \"${YELLOW}Next steps:${NC}\"\necho \"  Review upstream: ./Scripts/review_upstream.sh upstream\"\necho \"  Review quotio:   ./Scripts/review_upstream.sh quotio\"\necho \"  Detailed diff:   git diff main..upstream/main\"\necho \"  View quotio:     git log -p quotio/main~10..quotio/main\"\n\n"
  },
  {
    "path": "Scripts/compile_and_run.sh",
    "content": "#!/usr/bin/env bash\n# Reset CodexBar: kill running instances, build, package, relaunch, verify.\n\nset -euo pipefail\n\nROOT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")/..\" && pwd)\"\nAPP_BUNDLE=\"${ROOT_DIR}/CodexBar.app\"\nAPP_PROCESS_PATTERN=\"CodexBar.app/Contents/MacOS/CodexBar\"\nDEBUG_PROCESS_PATTERN=\"${ROOT_DIR}/.build/debug/CodexBar\"\nRELEASE_PROCESS_PATTERN=\"${ROOT_DIR}/.build/release/CodexBar\"\nLOCK_KEY=\"$(printf '%s' \"${ROOT_DIR}\" | shasum -a 256 | cut -c1-8)\"\nLOCK_DIR=\"${TMPDIR:-/tmp}/codexbar-compile-and-run-${LOCK_KEY}\"\nLOCK_PID_FILE=\"${LOCK_DIR}/pid\"\nWAIT_FOR_LOCK=0\nRUN_TESTS=0\nDEBUG_LLDB=0\nRELEASE_ARCHES=\"\"\nSIGNING_MODE=\"${CODEXBAR_SIGNING:-}\"\n\nlog()  { printf '%s\\n' \"$*\"; }\nfail() { printf 'ERROR: %s\\n' \"$*\" >&2; exit 1; }\n\nhas_signing_identity() {\n  local identity=\"${1:-}\"\n  if [[ -z \"${identity}\" ]]; then\n    return 1\n  fi\n  security find-identity -p codesigning -v 2>/dev/null | grep -F \"${identity}\" >/dev/null 2>&1\n}\n\nresolve_signing_mode() {\n  if [[ -n \"${SIGNING_MODE}\" ]]; then\n    return\n  fi\n\n  if [[ -n \"${APP_IDENTITY:-}\" ]]; then\n    if has_signing_identity \"${APP_IDENTITY}\"; then\n      SIGNING_MODE=\"identity\"\n      return\n    fi\n    log \"WARN: APP_IDENTITY not found in Keychain; falling back to adhoc signing.\"\n    SIGNING_MODE=\"adhoc\"\n    return\n  fi\n\n  local candidate=\"\"\n  for candidate in \\\n    \"Developer ID Application: Peter Steinberger (Y5PE65HELJ)\" \\\n    \"CodexBar Development\"\n  do\n    if has_signing_identity \"${candidate}\"; then\n      APP_IDENTITY=\"${candidate}\"\n      export APP_IDENTITY\n      SIGNING_MODE=\"identity\"\n      return\n    fi\n  done\n\n  SIGNING_MODE=\"adhoc\"\n}\n\nrun_step() {\n  local label=\"$1\"; shift\n  log \"==> ${label}\"\n  if ! \"$@\"; then\n    fail \"${label} failed\"\n  fi\n}\n\ncleanup() {\n  if [[ -d \"${LOCK_DIR}\" ]]; then\n    rm -rf \"${LOCK_DIR}\"\n  fi\n}\n\nacquire_lock() {\n  while true; do\n    if mkdir \"${LOCK_DIR}\" 2>/dev/null; then\n      echo \"$$\" > \"${LOCK_PID_FILE}\"\n      return 0\n    fi\n\n    local existing_pid=\"\"\n    if [[ -f \"${LOCK_PID_FILE}\" ]]; then\n      existing_pid=\"$(cat \"${LOCK_PID_FILE}\" 2>/dev/null || true)\"\n    fi\n\n    if [[ -n \"${existing_pid}\" ]] && kill -0 \"${existing_pid}\" 2>/dev/null; then\n      if [[ \"${WAIT_FOR_LOCK}\" == \"1\" ]]; then\n        log \"==> Another agent is compiling (pid ${existing_pid}); waiting...\"\n        while kill -0 \"${existing_pid}\" 2>/dev/null; do\n          sleep 1\n        done\n        continue\n      fi\n      log \"==> Another agent is compiling (pid ${existing_pid}); re-run with --wait.\"\n      exit 0\n    fi\n\n    rm -rf \"${LOCK_DIR}\"\n  done\n}\n\ntrap cleanup EXIT INT TERM\n\nkill_claude_probes() {\n  # CodexBar spawns `claude /usage` + `/status` in a PTY; if we kill the app mid-probe we can orphan them.\n  pkill -f \"claude (/status|/usage) --allowed-tools\" 2>/dev/null || true\n  sleep 0.2\n  pkill -9 -f \"claude (/status|/usage) --allowed-tools\" 2>/dev/null || true\n}\n\nkill_all_codexbar() {\n  is_running() {\n    pgrep -f \"${APP_PROCESS_PATTERN}\" >/dev/null 2>&1 \\\n      || pgrep -f \"${DEBUG_PROCESS_PATTERN}\" >/dev/null 2>&1 \\\n      || pgrep -f \"${RELEASE_PROCESS_PATTERN}\" >/dev/null 2>&1 \\\n      || pgrep -x \"CodexBar\" >/dev/null 2>&1\n  }\n\n  # Phase 1: request termination (give the app time to exit cleanly).\n  for _ in {1..25}; do\n    pkill -f \"${APP_PROCESS_PATTERN}\" 2>/dev/null || true\n    pkill -f \"${DEBUG_PROCESS_PATTERN}\" 2>/dev/null || true\n    pkill -f \"${RELEASE_PROCESS_PATTERN}\" 2>/dev/null || true\n    pkill -x \"CodexBar\" 2>/dev/null || true\n    if ! is_running; then\n      return 0\n    fi\n    sleep 0.2\n  done\n\n  # Phase 2: force kill any stragglers (avoids `open -n` creating multiple instances).\n  pkill -9 -f \"${APP_PROCESS_PATTERN}\" 2>/dev/null || true\n  pkill -9 -f \"${DEBUG_PROCESS_PATTERN}\" 2>/dev/null || true\n  pkill -9 -f \"${RELEASE_PROCESS_PATTERN}\" 2>/dev/null || true\n  pkill -9 -x \"CodexBar\" 2>/dev/null || true\n\n  for _ in {1..25}; do\n    if ! is_running; then\n      return 0\n    fi\n    sleep 0.2\n  done\n\n  fail \"Failed to kill all CodexBar instances.\"\n}\n\n# 1) Ensure a single runner instance.\nfor arg in \"$@\"; do\n  case \"${arg}\" in\n    --wait|-w) WAIT_FOR_LOCK=1 ;;\n    --test|-t) RUN_TESTS=1 ;;\n    --debug-lldb) DEBUG_LLDB=1 ;;\n    --release-universal) RELEASE_ARCHES=\"arm64 x86_64\" ;;\n    --release-arches=*) RELEASE_ARCHES=\"${arg#*=}\" ;;\n    --help|-h)\n      log \"Usage: $(basename \"$0\") [--wait] [--test] [--debug-lldb] [--release-universal] [--release-arches=\\\"arm64 x86_64\\\"]\"\n      exit 0\n      ;;\n    *)\n      ;;\n  esac\ndone\n\nresolve_signing_mode\nif [[ \"${SIGNING_MODE}\" == \"adhoc\" ]]; then\n  log \"==> Signing: adhoc (set APP_IDENTITY or install a dev cert to avoid keychain prompts)\"\nelse\n  log \"==> Signing: ${APP_IDENTITY:-Developer ID Application}\"\nfi\n\nacquire_lock\n\n# 2) Kill all running CodexBar instances (debug, release, bundled).\nlog \"==> Killing existing CodexBar instances\"\nkill_all_codexbar\nkill_claude_probes\n\n# 2.5) Delete keychain entries to avoid permission prompts with adhoc signing\n# (adhoc signature changes on every build, making old keychain entries inaccessible)\nif [[ \"${SIGNING_MODE:-adhoc}\" == \"adhoc\" ]]; then\n  log \"==> Clearing keychain entries (adhoc signing)\"\n  security delete-generic-password -s \"com.steipete.CodexBar\" 2>/dev/null || true\n  # Clear all keychain items for the app to avoid multiple prompts\n  while security delete-generic-password -s \"com.steipete.CodexBar\" 2>/dev/null; do\n    :\n  done\nfi\n\n# 3) Package (release build happens inside package_app.sh).\nif [[ \"${RUN_TESTS}\" == \"1\" ]]; then\n  run_step \"swift test\" swift test -q\nfi\nif [[ \"${DEBUG_LLDB}\" == \"1\" && -n \"${RELEASE_ARCHES}\" ]]; then\n  fail \"--release-arches is only supported for release packaging\"\nfi\nHOST_ARCH=\"$(uname -m)\"\nARCHES_VALUE=\"${HOST_ARCH}\"\nif [[ -n \"${RELEASE_ARCHES}\" ]]; then\n  ARCHES_VALUE=\"${RELEASE_ARCHES}\"\nfi\nif [[ \"${DEBUG_LLDB}\" == \"1\" ]]; then\n  run_step \"package app\" env CODEXBAR_ALLOW_LLDB=1 ARCHES=\"${ARCHES_VALUE}\" \"${ROOT_DIR}/Scripts/package_app.sh\" debug\nelse\n  if [[ -n \"${SIGNING_MODE}\" ]]; then\n    run_step \"package app\" env CODEXBAR_SIGNING=\"${SIGNING_MODE}\" ARCHES=\"${ARCHES_VALUE}\" \"${ROOT_DIR}/Scripts/package_app.sh\"\n  else\n    run_step \"package app\" env ARCHES=\"${ARCHES_VALUE}\" \"${ROOT_DIR}/Scripts/package_app.sh\"\n  fi\nfi\n\n# 4) Launch the packaged app.\nlog \"==> launch app\"\nif ! open \"${APP_BUNDLE}\"; then\n  log \"WARN: launch app returned non-zero; falling back to direct binary launch.\"\n  \"${APP_BUNDLE}/Contents/MacOS/CodexBar\" >/dev/null 2>&1 &\n  disown\nfi\n\n# 5) Verify the app stays up for at least a moment (launch can be >1s on some systems).\nfor _ in {1..10}; do\n  if pgrep -f \"${APP_PROCESS_PATTERN}\" >/dev/null 2>&1; then\n    log \"OK: CodexBar is running.\"\n    exit 0\n  fi\n  sleep 0.4\ndone\nfail \"App exited immediately. Check crash logs in Console.app (User Reports).\"\n"
  },
  {
    "path": "Scripts/docs-list.mjs",
    "content": "#!/usr/bin/env node\nimport { readdirSync, readFileSync } from 'node:fs';\nimport { dirname, join, relative } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst DOCS_DIR = join(dirname(fileURLToPath(import.meta.url)), '..', 'docs');\nconst EXCLUDED_DIRS = new Set(['archive', 'research']);\n\nfunction walkMarkdownFiles(dir, base = dir) {\n  const entries = readdirSync(dir, { withFileTypes: true });\n  const files = [];\n\n  for (const entry of entries) {\n    if (entry.name.startsWith('.')) continue;\n    const fullPath = join(dir, entry.name);\n\n    if (entry.isDirectory()) {\n      if (EXCLUDED_DIRS.has(entry.name)) continue;\n      files.push(...walkMarkdownFiles(fullPath, base));\n    } else if (entry.isFile() && entry.name.endsWith('.md')) {\n      files.push(relative(base, fullPath));\n    }\n  }\n\n  return files.sort((a, b) => a.localeCompare(b));\n}\n\nfunction extractMetadata(fullPath) {\n  const content = readFileSync(fullPath, 'utf8');\n\n  if (!content.startsWith('---')) {\n    return { summary: null, readWhen: [], error: 'missing front matter' };\n  }\n\n  const endIndex = content.indexOf('\\n---', 3);\n  if (endIndex === -1) {\n    return { summary: null, readWhen: [], error: 'unterminated front matter' };\n  }\n\n  const frontMatter = content.slice(3, endIndex).trim();\n  const lines = frontMatter.split('\\n');\n\n  let summaryLine = null;\n  const readWhen = [];\n  let collectingReadWhen = false;\n\n  for (const rawLine of lines) {\n    const line = rawLine.trim();\n\n    if (line.startsWith('summary:')) {\n      summaryLine = line;\n      collectingReadWhen = false;\n      continue;\n    }\n\n    if (line.startsWith('read_when:')) {\n      collectingReadWhen = true;\n      const inline = line.slice('read_when:'.length).trim();\n      if (inline.startsWith('[') && inline.endsWith(']')) {\n        try {\n          const parsed = JSON.parse(inline.replace(/'/g, '\"'));\n          if (Array.isArray(parsed)) {\n            parsed\n              .map((v) => String(v).trim())\n              .filter(Boolean)\n              .forEach((v) => readWhen.push(v));\n          }\n        } catch {\n          /* ignore malformed inline */\n        }\n      }\n      continue;\n    }\n\n    if (collectingReadWhen) {\n      if (line.startsWith('- ')) {\n        const hint = line.slice(2).trim();\n        if (hint) readWhen.push(hint);\n      } else if (line === '') {\n        // allow blank spacer lines inside list\n      } else {\n        collectingReadWhen = false;\n      }\n    }\n  }\n\n  if (!summaryLine) {\n    return { summary: null, readWhen, error: 'summary key missing' };\n  }\n\n  const summaryValue = summaryLine.slice('summary:'.length).trim();\n  const normalized = summaryValue.replace(/^['\"]|['\"]$/g, '').replace(/\\s+/g, ' ').trim();\n\n  if (!normalized) {\n    return { summary: null, readWhen, error: 'summary is empty' };\n  }\n\n  return { summary: normalized, readWhen };\n}\n\nconsole.log('Listing all markdown files in docs folder:');\n\nconst markdownFiles = walkMarkdownFiles(DOCS_DIR);\n\nfor (const relativePath of markdownFiles) {\n  const fullPath = join(DOCS_DIR, relativePath);\n  const { summary, readWhen, error } = extractMetadata(fullPath);\n  if (summary) {\n    console.log(`${relativePath} - ${summary}`);\n    if (readWhen.length > 0) {\n      console.log(`  Read when: ${readWhen.join('; ')}`);\n    }\n  } else {\n    const reason = error ? ` - [${error}]` : '';\n    console.log(`${relativePath}${reason}`);\n  }\n}\n\nconsole.log('\\nReminder: keep docs up to date as behavior changes. When your task matches any \"Read when\" hint above (React hooks, cache directives, database work, tests, etc.), read that doc before coding, and suggest new coverage when it is missing.');\n"
  },
  {
    "path": "Scripts/install_lint_tools.sh",
    "content": "#!/usr/bin/env bash\n\nset -euo pipefail\n\nROOT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")/..\" && pwd)\"\nTOOLS_DIR=\"${ROOT_DIR}/.build/lint-tools\"\nBIN_DIR=\"${TOOLS_DIR}/bin\"\n\nSWIFTFORMAT_VERSION=\"0.59.1\"\nSWIFTLINT_VERSION=\"0.63.2\"\n\nSWIFTFORMAT_SHA256_DARWIN=\"8b6289b608a44e73cd3851c3589dbd7c553f32cc805aa54b3a496ce2b90febe7\"\nSWIFTLINT_SHA256_DARWIN=\"c59a405c85f95b92ced677a500804e081596a4cae4a6a485af76065557d6ed29\"\n\nlog() { printf '%s\\n' \"$*\"; }\nfail() { printf 'ERROR: %s\\n' \"$*\" >&2; exit 1; }\n\nsha256_value() {\n  local path=\"$1\"\n  if command -v shasum >/dev/null 2>&1; then\n    shasum -a 256 \"$path\" | awk '{print $1}'\n    return 0\n  fi\n  if command -v sha256sum >/dev/null 2>&1; then\n    sha256sum \"$path\" | awk '{print $1}'\n    return 0\n  fi\n  fail \"Missing shasum/sha256sum.\"\n}\n\ndownload_file() {\n  local url=\"$1\"\n  local out=\"$2\"\n  curl -fL --retry 3 --retry-connrefused --retry-delay 2 -o \"$out\" \"$url\"\n}\n\ninstall_zip_binary() {\n  local label=\"$1\"\n  local url=\"$2\"\n  local expected_sha=\"$3\"\n  local binary_name=\"$4\"\n\n  local tmp_zip\n  tmp_zip=\"$(mktemp -t \"${label}.XXXX\")\"\n  local tmp_dir\n  tmp_dir=\"$(mktemp -d -t \"${label}.XXXX\")\"\n\n  log \"==> Downloading ${label}\"\n  download_file \"$url\" \"$tmp_zip\"\n\n  local actual_sha\n  actual_sha=\"$(sha256_value \"$tmp_zip\")\"\n  if [[ -n \"$expected_sha\" && \"$actual_sha\" != \"$expected_sha\" ]]; then\n    rm -f \"$tmp_zip\"\n    rm -rf \"$tmp_dir\"\n    fail \"${label} SHA256 mismatch (expected ${expected_sha}, got ${actual_sha})\"\n  fi\n\n  unzip -q \"$tmp_zip\" -d \"$tmp_dir\"\n\n  local extracted_path=\"\"\n  if [[ -f \"${tmp_dir}/${binary_name}\" ]]; then\n    extracted_path=\"${tmp_dir}/${binary_name}\"\n  else\n    extracted_path=\"$(find \"$tmp_dir\" -type f -name \"$binary_name\" | head -n 1 || true)\"\n  fi\n\n  if [[ -z \"$extracted_path\" || ! -f \"$extracted_path\" ]]; then\n    rm -f \"$tmp_zip\"\n    rm -rf \"$tmp_dir\"\n    fail \"${label} binary '${binary_name}' not found in archive\"\n  fi\n\n  install -m 0755 \"$extracted_path\" \"${BIN_DIR}/${binary_name}\"\n\n  rm -f \"$tmp_zip\"\n  rm -rf \"$tmp_dir\"\n}\n\nmkdir -p \"$BIN_DIR\"\n\nif [[ -x \"${BIN_DIR}/swiftformat\" && -x \"${BIN_DIR}/swiftlint\" ]]; then\n  if [[ \"$(\"${BIN_DIR}/swiftformat\" --version 2>/dev/null || true)\" == \"${SWIFTFORMAT_VERSION}\" ]] \\\n    && [[ \"$(\"${BIN_DIR}/swiftlint\" version 2>/dev/null || true)\" == \"${SWIFTLINT_VERSION}\" ]]\n  then\n    log \"==> Lint tools already installed (${SWIFTFORMAT_VERSION}, ${SWIFTLINT_VERSION})\"\n    exit 0\n  fi\nfi\n\nOS=\"$(uname -s)\"\nARCH=\"$(uname -m)\"\n\ncase \"$OS\" in\n  Darwin)\n    SWIFTFORMAT_URL=\"https://github.com/nicklockwood/SwiftFormat/releases/download/${SWIFTFORMAT_VERSION}/swiftformat.zip\"\n    SWIFTLINT_URL=\"https://github.com/realm/SwiftLint/releases/download/${SWIFTLINT_VERSION}/portable_swiftlint.zip\"\n\n    install_zip_binary \"SwiftFormat ${SWIFTFORMAT_VERSION}\" \"$SWIFTFORMAT_URL\" \"$SWIFTFORMAT_SHA256_DARWIN\" \"swiftformat\"\n    install_zip_binary \"SwiftLint ${SWIFTLINT_VERSION}\" \"$SWIFTLINT_URL\" \"$SWIFTLINT_SHA256_DARWIN\" \"swiftlint\"\n    ;;\n  Linux)\n    case \"$ARCH\" in\n      x86_64)\n        SWIFTFORMAT_URL=\"https://github.com/nicklockwood/SwiftFormat/releases/download/${SWIFTFORMAT_VERSION}/swiftformat_linux.zip\"\n        SWIFTLINT_URL=\"https://github.com/realm/SwiftLint/releases/download/${SWIFTLINT_VERSION}/swiftlint_linux_amd64.zip\"\n        ;;\n      aarch64|arm64)\n        SWIFTFORMAT_URL=\"https://github.com/nicklockwood/SwiftFormat/releases/download/${SWIFTFORMAT_VERSION}/swiftformat_linux_aarch64.zip\"\n        SWIFTLINT_URL=\"https://github.com/realm/SwiftLint/releases/download/${SWIFTLINT_VERSION}/swiftlint_linux_arm64.zip\"\n        ;;\n      *)\n        fail \"Unsupported Linux arch: ${ARCH}\"\n        ;;\n    esac\n\n    # SHA256 is intentionally only enforced for the macOS CI path.\n    # If we later run lint on Linux CI, add pinned SHAs here as well.\n    log \"WARN: Linux SHA256 verification not configured for ${ARCH}; installing anyway.\"\n    install_zip_binary \"SwiftFormat ${SWIFTFORMAT_VERSION}\" \"$SWIFTFORMAT_URL\" \"\" \"swiftformat\"\n    install_zip_binary \"SwiftLint ${SWIFTLINT_VERSION}\" \"$SWIFTLINT_URL\" \"\" \"swiftlint\"\n    ;;\n  *)\n    fail \"Unsupported OS: ${OS}\"\n    ;;\nesac\n\nlog \"==> Installed lint tools to ${BIN_DIR}\"\n\"${BIN_DIR}/swiftformat\" --version\n\"${BIN_DIR}/swiftlint\" version\n"
  },
  {
    "path": "Scripts/launch.sh",
    "content": "#!/bin/bash\nset -euo pipefail\n\n# Simple script to launch CodexBar (kills existing instance first)\n# Usage: ./Scripts/launch.sh\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nPROJECT_ROOT=\"$(cd \"$SCRIPT_DIR/..\" && pwd)\"\nAPP_PATH=\"$PROJECT_ROOT/CodexBar.app\"\n\necho \"==> Killing existing CodexBar instances\"\npkill -x CodexBar || pkill -f CodexBar.app || true\nsleep 0.5\n\nif [[ ! -d \"$APP_PATH\" ]]; then\n    echo \"ERROR: CodexBar.app not found at $APP_PATH\"\n    echo \"Run ./Scripts/package_app.sh first to build the app\"\n    exit 1\nfi\n\necho \"==> Launching CodexBar from $APP_PATH\"\nopen -n \"$APP_PATH\"\n\n# Wait a moment and check if it's running\nsleep 1\nif pgrep -x CodexBar > /dev/null; then\n    echo \"OK: CodexBar is running.\"\nelse\n    echo \"ERROR: App exited immediately. Check crash logs in Console.app (User Reports).\"\n    exit 1\nfi\n\n"
  },
  {
    "path": "Scripts/lint.sh",
    "content": "#!/usr/bin/env bash\n\nset -euo pipefail\n\nROOT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")/..\" && pwd)\"\nBIN_DIR=\"${ROOT_DIR}/.build/lint-tools/bin\"\n\nensure_tools() {\n  # Always delegate to the installer so pinned versions are enforced.\n  # The installer is idempotent and exits early when the expected versions are already present.\n  \"${ROOT_DIR}/Scripts/install_lint_tools.sh\"\n}\n\ncmd=\"${1:-lint}\"\n\ncase \"$cmd\" in\n  lint)\n    ensure_tools\n    \"${BIN_DIR}/swiftformat\" Sources Tests --lint\n    \"${BIN_DIR}/swiftlint\" --strict\n    ;;\n  format)\n    ensure_tools\n    \"${BIN_DIR}/swiftformat\" Sources Tests\n    ;;\n  *)\n    printf 'Usage: %s [lint|format]\\n' \"$(basename \"$0\")\" >&2\n    exit 2\n    ;;\nesac\n"
  },
  {
    "path": "Scripts/make_appcast.sh",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\nROOT=$(cd \"$(dirname \"$0\")/..\" && pwd)\nZIP=${1:?\n\"Usage: $0 CodexBar-<ver>.zip\"}\nFEED_URL=${2:-\"https://raw.githubusercontent.com/steipete/CodexBar/main/appcast.xml\"}\nPRIVATE_KEY_FILE=${SPARKLE_PRIVATE_KEY_FILE:-}\nSPARKLE_CHANNEL=${SPARKLE_CHANNEL:-}\nif [[ -z \"$PRIVATE_KEY_FILE\" ]]; then\n  echo \"Set SPARKLE_PRIVATE_KEY_FILE to your ed25519 private key (Sparkle).\" >&2\n  exit 1\nfi\nif [[ ! -f \"$ZIP\" ]]; then\n  echo \"Zip not found: $ZIP\" >&2\n  exit 1\nfi\n\nZIP_DIR=$(cd \"$(dirname \"$ZIP\")\" && pwd)\nZIP_NAME=$(basename \"$ZIP\")\nZIP_BASE=\"${ZIP_NAME%.zip}\"\nVERSION=${SPARKLE_RELEASE_VERSION:-}\nif [[ -z \"$VERSION\" ]]; then\n  if [[ \"$ZIP_NAME\" =~ ^CodexBar-([0-9]+(\\.[0-9]+){1,2}([-.][^.]*)?)\\.zip$ ]]; then\n    VERSION=\"${BASH_REMATCH[1]}\"\n  else\n    echo \"Could not infer version from $ZIP_NAME; set SPARKLE_RELEASE_VERSION.\" >&2\n    exit 1\n  fi\nfi\n\nNOTES_HTML=\"${ZIP_DIR}/${ZIP_BASE}.html\"\nKEEP_NOTES=${KEEP_SPARKLE_NOTES:-0}\nif [[ -x \"$ROOT/Scripts/changelog-to-html.sh\" ]]; then\n  \"$ROOT/Scripts/changelog-to-html.sh\" \"$VERSION\" >\"$NOTES_HTML\"\nelse\n  echo \"Missing Scripts/changelog-to-html.sh; cannot generate HTML release notes.\" >&2\n  exit 1\nfi\ncleanup() {\n  if [[ -n \"${WORK_DIR:-}\" ]]; then\n    rm -rf \"$WORK_DIR\"\n  fi\n  if [[ \"$KEEP_NOTES\" != \"1\" ]]; then\n    rm -f \"$NOTES_HTML\"\n  fi\n}\ntrap cleanup EXIT\n\nDOWNLOAD_URL_PREFIX=${SPARKLE_DOWNLOAD_URL_PREFIX:-\"https://github.com/steipete/CodexBar/releases/download/v${VERSION}/\"}\n\n# Sparkle provides generate_appcast; ensure it's on PATH (via SwiftPM build of Sparkle's bin) or Xcode dmg\nif ! command -v generate_appcast >/dev/null; then\n  echo \"generate_appcast not found in PATH. Install Sparkle tools (see Sparkle docs).\" >&2\n  exit 1\nfi\n\nWORK_DIR=$(mktemp -d /tmp/codexbar-appcast.XXXXXX)\n\ncp \"$ROOT/appcast.xml\" \"$WORK_DIR/appcast.xml\"\ncp \"$ZIP\" \"$WORK_DIR/$ZIP_NAME\"\ncp \"$NOTES_HTML\" \"$WORK_DIR/$ZIP_BASE.html\"\n\npushd \"$WORK_DIR\" >/dev/null\ngenerate_appcast \\\n  --ed-key-file \"$PRIVATE_KEY_FILE\" \\\n  --download-url-prefix \"$DOWNLOAD_URL_PREFIX\" \\\n  --embed-release-notes \\\n  --link \"$FEED_URL\" \\\n  \"$WORK_DIR\"\npopd >/dev/null\n\nif [[ -n \"$SPARKLE_CHANNEL\" ]]; then\n  python3 - \"$WORK_DIR/appcast.xml\" \"$VERSION\" \"$SPARKLE_CHANNEL\" <<'PY'\nimport re\nimport sys\n\npath, version, channel = sys.argv[1], sys.argv[2], sys.argv[3]\nwith open(path, \"r\", encoding=\"utf-8\") as handle:\n    lines = handle.read().splitlines()\n\ntarget = f\"<sparkle:shortVersionString>{version}</sparkle:shortVersionString>\"\ntry:\n    index = next(i for i, line in enumerate(lines) if target in line)\nexcept StopIteration as exc:\n    raise SystemExit(f\"Could not find {target} in {path}\") from exc\n\nfor j in range(index, -1, -1):\n    if \"<item\" in lines[j]:\n        line = lines[j]\n        if \"sparkle:channel\" in line:\n            line = re.sub(r'sparkle:channel=\"[^\"]*\"', f'sparkle:channel=\"{channel}\"', line)\n        else:\n            line = line.replace(\"<item\", f'<item sparkle:channel=\"{channel}\"', 1)\n        lines[j] = line\n        break\nelse:\n    raise SystemExit(f\"Could not find <item> for version {version} in {path}\")\n\nwith open(path, \"w\", encoding=\"utf-8\") as handle:\n    handle.write(\"\\n\".join(lines) + \"\\n\")\nPY\n  echo \"Tagged ${VERSION} with sparkle:channel=\\\"${SPARKLE_CHANNEL}\\\"\"\nfi\n\ncp \"$WORK_DIR/appcast.xml\" \"$ROOT/appcast.xml\"\n\necho \"Appcast generated (appcast.xml). Upload alongside $ZIP at $FEED_URL\"\n"
  },
  {
    "path": "Scripts/package_app.sh",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\nCONF=${1:-release}\nALLOW_LLDB=${CODEXBAR_ALLOW_LLDB:-0}\nSIGNING_MODE=${CODEXBAR_SIGNING:-}\nROOT=$(cd \"$(dirname \"$0\")/..\" && pwd)\ncd \"$ROOT\"\n\n# Load version info\nsource \"$ROOT/version.env\"\n\n# Clean build only when explicitly requested (slower).\nif [[ \"${CODEXBAR_FORCE_CLEAN:-0}\" == \"1\" ]]; then\n  if [[ -d \"$ROOT/.build\" ]]; then\n    if command -v trash >/dev/null 2>&1; then\n      if ! trash \"$ROOT/.build\"; then\n        echo \"WARN: trash .build failed; continuing with swift package clean.\" >&2\n      fi\n    else\n      rm -rf \"$ROOT/.build\" || echo \"WARN: rm -rf .build failed; continuing with swift package clean.\" >&2\n    fi\n  fi\n  swift package clean >/dev/null 2>&1 || true\nfi\n\n# Build for host architecture by default; allow overriding via ARCHES (e.g., \"arm64 x86_64\" for universal).\nARCH_LIST=( ${ARCHES:-} )\nif [[ ${#ARCH_LIST[@]} -eq 0 ]]; then\n  HOST_ARCH=$(uname -m)\n  case \"$HOST_ARCH\" in\n    arm64) ARCH_LIST=(arm64) ;;\n    x86_64) ARCH_LIST=(x86_64) ;;\n    *) ARCH_LIST=(\"$HOST_ARCH\") ;;\n  esac\nfi\n\npatch_keyboard_shortcuts() {\n  local util_path=\"$ROOT/.build/checkouts/KeyboardShortcuts/Sources/KeyboardShortcuts/Utilities.swift\"\n  if [[ ! -f \"$util_path\" ]]; then\n    return 0\n  fi\n  if grep -q \"keyboardShortcutsSafeBundle\" \"$util_path\"; then\n    return 0\n  fi\n\n  chmod +w \"$util_path\" || true\n  python3 - \"$util_path\" <<'PY'\nimport sys\nfrom pathlib import Path\n\npath = Path(sys.argv[1])\ntext = path.read_text()\nif \".keyboardShortcutsSafeBundle\" in text:\n    sys.exit(0)\n\ntext = text.replace(\n    'NSLocalizedString(self, bundle: .module, comment: self)',\n    'NSLocalizedString(self, bundle: .keyboardShortcutsSafeBundle, comment: self)',\n)\n\ninject = \"\"\"\nprivate extension Bundle {\n    /// Safe lookup that avoids the fatal trap in the autogenerated `Bundle.module`\n    /// when the resource bundle is not placed at the bundle root.\n    static let keyboardShortcutsSafeBundle: Bundle = {\n        #if os(macOS)\n        if let url = Bundle.main.url(forResource: \"KeyboardShortcuts_KeyboardShortcuts\", withExtension: \"bundle\"),\n           let bundle = Bundle(url: url) {\n            return bundle\n        }\n\n        let rootURL = Bundle.main.bundleURL.appendingPathComponent(\"KeyboardShortcuts_KeyboardShortcuts.bundle\")\n        if let bundle = Bundle(url: rootURL) {\n            return bundle\n        }\n        #endif\n\n        let devURL = URL(fileURLWithPath: #file)\n            .deletingLastPathComponent()  // Utilities.swift\n            .deletingLastPathComponent()  // KeyboardShortcuts\n            .deletingLastPathComponent()  // Sources\n            .appendingPathComponent(\"KeyboardShortcuts_KeyboardShortcuts.bundle\")\n        if let bundle = Bundle(url: devURL) {\n            return bundle\n        }\n\n        return Bundle.main\n    }()\n}\n\"\"\"\n\nmarker = \"}\\n\\n\\nextension Data {\"\nif marker not in text:\n    raise SystemExit(\"Marker not found in Utilities.swift; patch failed.\")\n\ntext = text.replace(marker, \"}\\n\\n\" + inject + \"\\n\\nextension Data {\")\npath.write_text(text)\nPY\n}\n\nKEYBOARD_SHORTCUTS_UTIL=\"$ROOT/.build/checkouts/KeyboardShortcuts/Sources/KeyboardShortcuts/Utilities.swift\"\nif [[ ! -f \"$KEYBOARD_SHORTCUTS_UTIL\" ]]; then\n  swift build -c \"$CONF\" --arch \"${ARCH_LIST[0]}\"\nfi\npatch_keyboard_shortcuts\n\nfor ARCH in \"${ARCH_LIST[@]}\"; do\n  swift build -c \"$CONF\" --arch \"$ARCH\"\ndone\n\nAPP=\"$ROOT/CodexBar.app\"\nrm -rf \"$APP\"\nmkdir -p \"$APP/Contents/MacOS\" \"$APP/Contents/Resources\" \"$APP/Contents/Frameworks\"\nmkdir -p \"$APP/Contents/Helpers\" \"$APP/Contents/PlugIns\"\n\n# Convert new .icon bundle to .icns if present (macOS 14+/IconStudio export)\nICON_SOURCE=\"$ROOT/Icon.icon\"\nICON_TARGET=\"$ROOT/Icon.icns\"\nif [[ -f \"$ICON_SOURCE\" ]]; then\n  iconutil --convert icns --output \"$ICON_TARGET\" \"$ICON_SOURCE\"\nfi\n\nBUNDLE_ID=\"com.steipete.codexbar\"\nFEED_URL=\"https://raw.githubusercontent.com/steipete/CodexBar/main/appcast.xml\"\nAUTO_CHECKS=true\nLOWER_CONF=$(printf \"%s\" \"$CONF\" | tr '[:upper:]' '[:lower:]')\nif [[ \"$LOWER_CONF\" == \"debug\" ]]; then\n  BUNDLE_ID=\"com.steipete.codexbar.debug\"\n  FEED_URL=\"\"\n  AUTO_CHECKS=false\nfi\nif [[ \"$SIGNING_MODE\" == \"adhoc\" ]]; then\n  FEED_URL=\"\"\n  AUTO_CHECKS=false\nfi\nWIDGET_BUNDLE_ID=\"${BUNDLE_ID}.widget\"\nAPP_GROUP_ID=\"group.com.steipete.codexbar\"\nif [[ \"$BUNDLE_ID\" == *\".debug\"* ]]; then\n  APP_GROUP_ID=\"group.com.steipete.codexbar.debug\"\nfi\nENTITLEMENTS_DIR=\"$ROOT/.build/entitlements\"\nAPP_ENTITLEMENTS=\"${ENTITLEMENTS_DIR}/CodexBar.entitlements\"\nWIDGET_ENTITLEMENTS=\"${ENTITLEMENTS_DIR}/CodexBarWidget.entitlements\"\nmkdir -p \"$ENTITLEMENTS_DIR\"\nif [[ \"$ALLOW_LLDB\" == \"1\" && \"$LOWER_CONF\" != \"debug\" ]]; then\n  echo \"ERROR: CODEXBAR_ALLOW_LLDB requires debug configuration\" >&2\n  exit 1\nfi\ncat > \"$APP_ENTITLEMENTS\" <<PLIST\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n    <key>com.apple.security.application-groups</key>\n    <array>\n        <string>${APP_GROUP_ID}</string>\n    </array>\n    $(if [[ \"$ALLOW_LLDB\" == \"1\" ]]; then echo \"    <key>com.apple.security.get-task-allow</key><true/>\"; fi)\n</dict>\n</plist>\nPLIST\ncat > \"$WIDGET_ENTITLEMENTS\" <<PLIST\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n    <key>com.apple.security.app-sandbox</key>\n    <true/>\n    <key>com.apple.security.application-groups</key>\n    <array>\n        <string>${APP_GROUP_ID}</string>\n    </array>\n</dict>\n</plist>\nPLIST\nBUILD_TIMESTAMP=$(date -u +\"%Y-%m-%dT%H:%M:%SZ\")\nGIT_COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo \"unknown\")\n\ncat > \"$APP/Contents/Info.plist\" <<PLIST\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n    <key>CFBundleName</key><string>CodexBar</string>\n    <key>CFBundleDisplayName</key><string>CodexBar</string>\n    <key>CFBundleIdentifier</key><string>${BUNDLE_ID}</string>\n    <key>CFBundleExecutable</key><string>CodexBar</string>\n    <key>CFBundlePackageType</key><string>APPL</string>\n    <key>CFBundleShortVersionString</key><string>${MARKETING_VERSION}</string>\n    <key>CFBundleVersion</key><string>${BUILD_NUMBER}</string>\n    <key>LSMinimumSystemVersion</key><string>14.0</string>\n    <key>LSUIElement</key><true/>\n    <key>CFBundleIconFile</key><string>Icon</string>\n    <key>NSHumanReadableCopyright</key><string>© 2025 Peter Steinberger. MIT License.</string>\n    <key>SUFeedURL</key><string>${FEED_URL}</string>\n    <key>SUPublicEDKey</key><string>AGCY8w5vHirVfGGDGc8Szc5iuOqupZSh9pMj/Qs67XI=</string>\n    <key>SUEnableAutomaticChecks</key><${AUTO_CHECKS}/>\n    <key>CodexBuildTimestamp</key><string>${BUILD_TIMESTAMP}</string>\n    <key>CodexGitCommit</key><string>${GIT_COMMIT}</string>\n</dict>\n</plist>\nPLIST\n\nbuild_product_path() {\n  local name=\"$1\"\n  local arch=\"$2\"\n  case \"$arch\" in\n    arm64|x86_64) echo \".build/${arch}-apple-macosx/$CONF/$name\" ;;\n    *) echo \".build/$CONF/$name\" ;;\n  esac\n}\n\n# Resolve path to built binary; some SwiftPM versions use .build/$CONF/ when building for host only.\nresolve_binary_path() {\n  local name=\"$1\"\n  local arch=\"$2\"\n  local candidate\n  candidate=$(build_product_path \"$name\" \"$arch\")\n  if [[ -f \"$candidate\" ]]; then\n    echo \"$candidate\"\n    return\n  fi\n  if [[ \"$arch\" == \"arm64\" || \"$arch\" == \"x86_64\" ]] && [[ -f \".build/$CONF/$name\" ]]; then\n    echo \".build/$CONF/$name\"\n  fi\n}\n\nverify_binary_arches() {\n  local binary=\"$1\"; shift\n  local expected=(\"$@\")\n  local actual\n  actual=$(lipo -archs \"$binary\")\n  local actual_count expected_count\n  actual_count=$(wc -w <<<\"$actual\" | tr -d ' ')\n  expected_count=${#expected[@]}\n  if [[ \"$actual_count\" -ne \"$expected_count\" ]]; then\n    echo \"ERROR: $binary arch mismatch (expected: ${expected[*]}, actual: ${actual})\" >&2\n    exit 1\n  fi\n  for arch in \"${expected[@]}\"; do\n    if [[ \"$actual\" != *\"$arch\"* ]]; then\n      echo \"ERROR: $binary missing arch $arch (have: ${actual})\" >&2\n      exit 1\n    fi\n  done\n}\n\ninstall_binary() {\n  local name=\"$1\"\n  local dest=\"$2\"\n  local binaries=()\n  for arch in \"${ARCH_LIST[@]}\"; do\n    local src\n    src=$(resolve_binary_path \"$name\" \"$arch\")\n    if [[ -z \"$src\" || ! -f \"$src\" ]]; then\n      echo \"ERROR: Missing ${name} build for ${arch} at $(build_product_path \"$name\" \"$arch\")\" >&2\n      exit 1\n    fi\n    binaries+=(\"$src\")\n  done\n  if [[ ${#ARCH_LIST[@]} -gt 1 ]]; then\n    lipo -create \"${binaries[@]}\" -output \"$dest\"\n  else\n    cp \"${binaries[0]}\" \"$dest\"\n  fi\n  chmod +x \"$dest\"\n  verify_binary_arches \"$dest\" \"${ARCH_LIST[@]}\"\n}\n\ninstall_binary \"CodexBar\" \"$APP/Contents/MacOS/CodexBar\"\n# Ship CodexBarCLI alongside the app for easy symlinking.\nif [[ -n \"$(resolve_binary_path \"CodexBarCLI\" \"${ARCH_LIST[0]}\")\" ]]; then\n  install_binary \"CodexBarCLI\" \"$APP/Contents/Helpers/CodexBarCLI\"\nfi\n# Watchdog helper: ensures `claude` probes die when CodexBar crashes/gets killed.\nif [[ -n \"$(resolve_binary_path \"CodexBarClaudeWatchdog\" \"${ARCH_LIST[0]}\")\" ]]; then\n  install_binary \"CodexBarClaudeWatchdog\" \"$APP/Contents/Helpers/CodexBarClaudeWatchdog\"\nfi\nif [[ -n \"$(resolve_binary_path \"CodexBarWidget\" \"${ARCH_LIST[0]}\")\" ]]; then\n  WIDGET_APP=\"$APP/Contents/PlugIns/CodexBarWidget.appex\"\n  mkdir -p \"$WIDGET_APP/Contents/MacOS\" \"$WIDGET_APP/Contents/Resources\"\n  cat > \"$WIDGET_APP/Contents/Info.plist\" <<PLIST\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n    <key>CFBundleName</key><string>CodexBarWidget</string>\n    <key>CFBundleDisplayName</key><string>CodexBar</string>\n    <key>CFBundleIdentifier</key><string>${WIDGET_BUNDLE_ID}</string>\n    <key>CFBundleExecutable</key><string>CodexBarWidget</string>\n    <key>CFBundlePackageType</key><string>XPC!</string>\n    <key>CFBundleShortVersionString</key><string>${MARKETING_VERSION}</string>\n    <key>CFBundleVersion</key><string>${BUILD_NUMBER}</string>\n    <key>LSMinimumSystemVersion</key><string>14.0</string>\n    <key>NSExtension</key>\n    <dict>\n        <key>NSExtensionPointIdentifier</key><string>com.apple.widgetkit-extension</string>\n        <key>NSExtensionPrincipalClass</key><string>CodexBarWidget.CodexBarWidgetBundle</string>\n    </dict>\n</dict>\n</plist>\nPLIST\n  install_binary \"CodexBarWidget\" \"$WIDGET_APP/Contents/MacOS/CodexBarWidget\"\nfi\n# Embed Sparkle.framework\nif [[ -d \".build/$CONF/Sparkle.framework\" ]]; then\n  cp -R \".build/$CONF/Sparkle.framework\" \"$APP/Contents/Frameworks/\"\n  chmod -R a+rX \"$APP/Contents/Frameworks/Sparkle.framework\"\n  install_name_tool -add_rpath \"@executable_path/../Frameworks\" \"$APP/Contents/MacOS/CodexBar\"\n  # Re-sign Sparkle and all nested components with Developer ID + timestamp\n  SPARKLE=\"$APP/Contents/Frameworks/Sparkle.framework\"\nif [[ \"$SIGNING_MODE\" == \"adhoc\" ]]; then\n  CODESIGN_ID=\"-\"\n  CODESIGN_ARGS=(--force --sign \"$CODESIGN_ID\")\nelif [[ \"$ALLOW_LLDB\" == \"1\" ]]; then\n  CODESIGN_ID=\"-\"\n  CODESIGN_ARGS=(--force --sign \"$CODESIGN_ID\")\nelse\n  CODESIGN_ID=\"${APP_IDENTITY:-Developer ID Application: Peter Steinberger (Y5PE65HELJ)}\"\n  CODESIGN_ARGS=(--force --timestamp --options runtime --sign \"$CODESIGN_ID\")\nfi\nfunction resign() { codesign \"${CODESIGN_ARGS[@]}\" \"$1\"; }\n  # Sign innermost binaries first, then the framework root to seal resources\n  resign \"$SPARKLE\"\n  resign \"$SPARKLE/Versions/B/Sparkle\"\n  resign \"$SPARKLE/Versions/B/Autoupdate\"\n  resign \"$SPARKLE/Versions/B/Updater.app\"\n  resign \"$SPARKLE/Versions/B/Updater.app/Contents/MacOS/Updater\"\n  resign \"$SPARKLE/Versions/B/XPCServices/Downloader.xpc\"\n  resign \"$SPARKLE/Versions/B/XPCServices/Downloader.xpc/Contents/MacOS/Downloader\"\n  resign \"$SPARKLE/Versions/B/XPCServices/Installer.xpc\"\n  resign \"$SPARKLE/Versions/B/XPCServices/Installer.xpc/Contents/MacOS/Installer\"\n  resign \"$SPARKLE/Versions/B\"\n  resign \"$SPARKLE\"\nfi\n\nif [[ -f \"$ICON_TARGET\" ]]; then\n  cp \"$ICON_TARGET\" \"$APP/Contents/Resources/Icon.icns\"\nfi\n\n# Bundle app resources (provider icons, etc.).\nAPP_RESOURCES_DIR=\"$ROOT/Sources/CodexBar/Resources\"\nif [[ -d \"$APP_RESOURCES_DIR\" ]]; then\n  cp -R \"$APP_RESOURCES_DIR/.\" \"$APP/Contents/Resources/\"\nfi\nif [[ ! -f \"$APP/Contents/Resources/Icon-classic.icns\" ]]; then\n  echo \"ERROR: Missing Icon-classic.icns in app bundle resources.\" >&2\n  exit 1\nfi\n\n# SwiftPM resource bundles (e.g. KeyboardShortcuts) are emitted next to the built binary.\nCODEXBAR_BINARY=\"$(resolve_binary_path \"CodexBar\" \"${ARCH_LIST[0]}\")\"\nPREFERRED_BUILD_DIR=\"$(dirname \"${CODEXBAR_BINARY:-$(build_product_path \"CodexBar\" \"${ARCH_LIST[0]}\")}\")\"\nshopt -s nullglob\nSWIFTPM_BUNDLES=(\"${PREFERRED_BUILD_DIR}/\"*.bundle)\nshopt -u nullglob\nif [[ ${#SWIFTPM_BUNDLES[@]} -gt 0 ]]; then\n  for bundle in \"${SWIFTPM_BUNDLES[@]}\"; do\n    bundle_name=\"$(basename \"$bundle\")\"\n    cp -R \"$bundle\" \"$APP/Contents/Resources/\"\n  done\nfi\nif [[ ! -d \"$APP/Contents/Resources/KeyboardShortcuts_KeyboardShortcuts.bundle\" ]]; then\n  echo \"ERROR: Missing KeyboardShortcuts SwiftPM resource bundle (Settings → Keyboard shortcut will crash).\" >&2\n  echo \"Expected: ${PREFERRED_BUILD_DIR}/KeyboardShortcuts_KeyboardShortcuts.bundle\" >&2\n  exit 1\nfi\n\n# Ensure contents are writable before stripping attributes and signing.\nchmod -R u+w \"$APP\"\n\n# Strip extended attributes to prevent AppleDouble (._*) files that break code sealing\nxattr -cr \"$APP\"\nfind \"$APP\" -name '._*' -delete\n\n# Sign helper binaries if present\nif [[ -f \"${APP}/Contents/Helpers/CodexBarCLI\" ]]; then\n  codesign \"${CODESIGN_ARGS[@]}\" \"${APP}/Contents/Helpers/CodexBarCLI\"\nfi\nif [[ -f \"${APP}/Contents/Helpers/CodexBarClaudeWatchdog\" ]]; then\n  codesign \"${CODESIGN_ARGS[@]}\" \"${APP}/Contents/Helpers/CodexBarClaudeWatchdog\"\nfi\n\n# Sign widget extension if present\nif [[ -d \"${APP}/Contents/PlugIns/CodexBarWidget.appex\" ]]; then\n  codesign \"${CODESIGN_ARGS[@]}\" \\\n    --entitlements \"$WIDGET_ENTITLEMENTS\" \\\n    \"$APP/Contents/PlugIns/CodexBarWidget.appex/Contents/MacOS/CodexBarWidget\"\n  codesign \"${CODESIGN_ARGS[@]}\" \\\n    --entitlements \"$WIDGET_ENTITLEMENTS\" \\\n    \"$APP/Contents/PlugIns/CodexBarWidget.appex\"\nfi\n\n# Finally sign the app bundle itself\ncodesign \"${CODESIGN_ARGS[@]}\" \\\n  --entitlements \"$APP_ENTITLEMENTS\" \\\n  \"$APP\"\n\necho \"Created $APP\"\n"
  },
  {
    "path": "Scripts/prepare_upstream_pr.sh",
    "content": "#!/bin/bash\n# Prepare a clean branch for upstream PR submission\n# Usage: ./Scripts/prepare_upstream_pr.sh <feature-name>\n\nset -e\n\nFEATURE_NAME=$1\n\n# Colors\nRED='\\033[0;31m'\nGREEN='\\033[0;32m'\nYELLOW='\\033[1;33m'\nBLUE='\\033[0;34m'\nNC='\\033[0m'\n\nif [ -z \"$FEATURE_NAME\" ]; then\n    echo -e \"${RED}Error: Feature name required${NC}\"\n    echo \"Usage: ./Scripts/prepare_upstream_pr.sh <feature-name>\"\n    echo \"\"\n    echo \"Examples:\"\n    echo \"  ./Scripts/prepare_upstream_pr.sh fix-cursor-bonus\"\n    echo \"  ./Scripts/prepare_upstream_pr.sh improve-cookie-handling\"\n    exit 1\nfi\n\nBRANCH_NAME=\"upstream-pr/$FEATURE_NAME\"\n\necho -e \"${BLUE}==> Fetching latest upstream...${NC}\"\ngit fetch upstream\n\necho -e \"${BLUE}==> Creating upstream PR branch from upstream/main...${NC}\"\ngit checkout upstream/main\ngit checkout -b \"$BRANCH_NAME\"\n\necho \"\"\necho -e \"${GREEN}==> Branch created: $BRANCH_NAME${NC}\"\necho \"\"\necho -e \"${YELLOW}⚠️  IMPORTANT: This branch is for UPSTREAM submission${NC}\"\necho \"\"\necho -e \"${BLUE}Guidelines for upstream PRs:${NC}\"\necho \"\"\necho \"✅ DO include:\"\necho \"  - Bug fixes that affect all users\"\necho \"  - Performance improvements\"\necho \"  - Provider enhancements (generic)\"\necho \"  - Documentation improvements\"\necho \"  - Test coverage\"\necho \"\"\necho \"❌ DO NOT include:\"\necho \"  - Fork branding (About.swift, PreferencesAboutPane.swift)\"\necho \"  - Fork-specific features (multi-account, etc.)\"\necho \"  - References to topoffunnel.com\"\necho \"  - Experimental features\"\necho \"\"\necho -e \"${BLUE}Next steps:${NC}\"\necho \"\"\necho \"1. Cherry-pick your commits (clean, no fork branding):\"\necho \"   ${GREEN}git cherry-pick <commit-hash>${NC}\"\necho \"\"\necho \"2. Or manually apply changes:\"\necho \"   ${GREEN}# Edit files${NC}\"\necho \"   ${GREEN}git add <files>${NC}\"\necho \"   ${GREEN}git commit -m 'fix: description'${NC}\"\necho \"\"\necho \"3. Ensure tests pass:\"\necho \"   ${GREEN}swift test${NC}\"\necho \"\"\necho \"4. Review changes:\"\necho \"   ${GREEN}git diff upstream/main${NC}\"\necho \"\"\necho \"5. Push to your fork:\"\necho \"   ${GREEN}git push origin $BRANCH_NAME${NC}\"\necho \"\"\necho \"6. Create PR on GitHub:\"\necho \"   ${GREEN}https://github.com/steipete/CodexBar/compare/main...topoffunnel:$BRANCH_NAME${NC}\"\necho \"\"\necho -e \"${YELLOW}Remember: Keep PRs small and focused for better merge chances!${NC}\"\n\n"
  },
  {
    "path": "Scripts/release.sh",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\nROOT=$(cd \"$(dirname \"$0\")/..\" && pwd)\ncd \"$ROOT\"\n\nsource \"$ROOT/version.env\"\nsource \"$HOME/Projects/agent-scripts/release/sparkle_lib.sh\"\n\nAPPCAST=\"$ROOT/appcast.xml\"\nAPP_NAME=\"CodexBar\"\nARTIFACT_PREFIX=\"CodexBar-\"\nBUNDLE_ID=\"com.steipete.codexbar\"\nTAG=\"v${MARKETING_VERSION}\"\n\nerr() { echo \"ERROR: $*\" >&2; exit 1; }\n\nrequire_clean_worktree\nensure_changelog_finalized \"$MARKETING_VERSION\"\nensure_appcast_monotonic \"$APPCAST\" \"$MARKETING_VERSION\" \"$BUILD_NUMBER\"\n\nswiftformat Sources Tests >/dev/null\nswiftlint --strict\nswift test\n\n# Note: run this script in the foreground; do not background it so it waits to completion.\n\"$ROOT/Scripts/sign-and-notarize.sh\"\n\nKEY_FILE=$(clean_key \"$SPARKLE_PRIVATE_KEY_FILE\")\ntrap 'rm -f \"$KEY_FILE\"' EXIT\n\nprobe_sparkle_key \"$KEY_FILE\"\n\nclear_sparkle_caches \"$BUNDLE_ID\"\n\nNOTES_FILE=$(mktemp /tmp/codexbar-notes.XXXXXX.md)\nextract_notes_from_changelog \"$MARKETING_VERSION\" \"$NOTES_FILE\"\ntrap 'rm -f \"$KEY_FILE\" \"$NOTES_FILE\"' EXIT\n\ngit tag -s -f -m \"${APP_NAME} ${MARKETING_VERSION}\" \"$TAG\"\ngit push -f origin \"$TAG\"\n\ngh release create \"$TAG\" ${APP_NAME}-${MARKETING_VERSION}.zip ${APP_NAME}-${MARKETING_VERSION}.dSYM.zip \\\n  --title \"${APP_NAME} ${MARKETING_VERSION}\" \\\n  --notes-file \"$NOTES_FILE\"\n\nSPARKLE_PRIVATE_KEY_FILE=\"$KEY_FILE\" \\\n  \"$ROOT/Scripts/make_appcast.sh\" \\\n  \"${APP_NAME}-${MARKETING_VERSION}.zip\" \\\n  \"https://raw.githubusercontent.com/steipete/CodexBar/main/appcast.xml\"\n\nverify_appcast_entry \"$APPCAST\" \"$MARKETING_VERSION\" \"$KEY_FILE\"\n\ngit add \"$APPCAST\"\ngit commit -m \"docs: update appcast for ${MARKETING_VERSION}\"\ngit push origin main\n\nif [[ \"${RUN_SPARKLE_UPDATE_TEST:-0}\" == \"1\" ]]; then\n  PREV_TAG=$(git tag --sort=-v:refname | sed -n '2p')\n  [[ -z \"$PREV_TAG\" ]] && err \"RUN_SPARKLE_UPDATE_TEST=1 set but no previous tag found\"\n  \"$ROOT/Scripts/test_live_update.sh\" \"$PREV_TAG\" \"v${MARKETING_VERSION}\"\nfi\n\ncheck_assets \"$TAG\" \"$ARTIFACT_PREFIX\"\n\ngit push origin --tags\n\necho \"Release ${MARKETING_VERSION} complete.\"\n"
  },
  {
    "path": "Scripts/review_upstream.sh",
    "content": "#!/bin/bash\n# Create a review branch for upstream changes\n# Usage: ./Scripts/review_upstream.sh [upstream|quotio]\n\nset -e\n\nUPSTREAM=${1:-upstream}\nDATE=$(date +%Y%m%d)\nBRANCH_NAME=\"upstream-sync/${UPSTREAM}-${DATE}\"\n\n# Colors\nRED='\\033[0;31m'\nGREEN='\\033[0;32m'\nYELLOW='\\033[1;33m'\nBLUE='\\033[0;34m'\nNC='\\033[0m'\n\nif [ \"$UPSTREAM\" != \"upstream\" ] && [ \"$UPSTREAM\" != \"quotio\" ]; then\n    echo -e \"${RED}Error: Must specify 'upstream' or 'quotio'${NC}\"\n    echo \"Usage: ./Scripts/review_upstream.sh [upstream|quotio]\"\n    exit 1\nfi\n\necho -e \"${BLUE}==> Creating review branch for $UPSTREAM...${NC}\"\ngit checkout main\ngit checkout -b \"$BRANCH_NAME\"\n\necho -e \"${BLUE}==> Fetching latest from $UPSTREAM...${NC}\"\ngit fetch \"$UPSTREAM\"\n\necho \"\"\necho -e \"${GREEN}==> Commits to review:${NC}\"\ngit log --oneline --graph main..\"$UPSTREAM\"/main | head -30\n\necho \"\"\necho -e \"${GREEN}==> File changes summary:${NC}\"\ngit diff --stat main..\"$UPSTREAM\"/main\n\necho \"\"\necho -e \"${YELLOW}==> Review branch created: $BRANCH_NAME${NC}\"\necho \"\"\necho -e \"${BLUE}Next steps:${NC}\"\necho \"\"\necho \"1. Review commits in detail:\"\necho \"   ${GREEN}git log -p main..$UPSTREAM/main${NC}\"\necho \"\"\necho \"2. View specific files:\"\necho \"   ${GREEN}git show $UPSTREAM/main:path/to/file${NC}\"\necho \"\"\necho \"3. Cherry-pick specific commits:\"\necho \"   ${GREEN}git cherry-pick <commit-hash>${NC}\"\necho \"\"\necho \"4. Or merge all changes:\"\necho \"   ${GREEN}git merge $UPSTREAM/main${NC}\"\necho \"\"\necho \"5. Test thoroughly:\"\necho \"   ${GREEN}./Scripts/compile_and_run.sh${NC}\"\necho \"\"\necho \"6. If satisfied, merge to main:\"\necho \"   ${GREEN}git checkout main && git merge $BRANCH_NAME${NC}\"\necho \"\"\necho \"7. Or discard review branch:\"\necho \"   ${GREEN}git checkout main && git branch -D $BRANCH_NAME${NC}\"\necho \"\"\n\n# Create a review log file\nLOG_FILE=\"upstream-review-${UPSTREAM}-${DATE}.txt\"\necho \"=== Upstream Review: $UPSTREAM @ $DATE ===\" > \"$LOG_FILE\"\necho \"\" >> \"$LOG_FILE\"\necho \"Commits:\" >> \"$LOG_FILE\"\ngit log --oneline main..\"$UPSTREAM\"/main >> \"$LOG_FILE\"\necho \"\" >> \"$LOG_FILE\"\necho \"File changes:\" >> \"$LOG_FILE\"\ngit diff --stat main..\"$UPSTREAM\"/main >> \"$LOG_FILE\"\n\necho -e \"${GREEN}Review log saved to: $LOG_FILE${NC}\"\n\n"
  },
  {
    "path": "Scripts/setup_dev_signing.sh",
    "content": "#!/usr/bin/env bash\n# Setup stable development code signing to reduce keychain prompts\nset -euo pipefail\n\necho \"🔐 Setting up stable development code signing...\"\necho \"\"\necho \"This will create a self-signed certificate that stays consistent across rebuilds,\"\necho \"reducing keychain permission prompts.\"\necho \"\"\n\n# Check if we already have a CodexBar development certificate\nCERT_NAME=\"CodexBar Development\"\nif security find-certificate -c \"$CERT_NAME\" >/dev/null 2>&1; then\n    echo \"✅ Certificate '$CERT_NAME' already exists!\"\n    echo \"\"\n    echo \"To use it, add this to your shell profile (~/.zshrc or ~/.bashrc):\"\n    echo \"\"\n    echo \"    export APP_IDENTITY='$CERT_NAME'\"\n    echo \"\"\n    echo \"Then restart your terminal and rebuild with ./Scripts/compile_and_run.sh\"\n    exit 0\nfi\n\necho \"Creating self-signed certificate '$CERT_NAME'...\"\necho \"\"\n\n# Create a temporary config file for the certificate\nTEMP_CONFIG=$(mktemp)\ntrap \"rm -f $TEMP_CONFIG\" EXIT\n\ncat > \"$TEMP_CONFIG\" <<EOF\n[ req ]\ndistinguished_name = req_distinguished_name\nx509_extensions = v3_req\nprompt = no\n\n[ req_distinguished_name ]\nCN = $CERT_NAME\nO = CodexBar Development\nC = US\n\n[ v3_req ]\nkeyUsage = critical,digitalSignature\nextendedKeyUsage = codeSigning\nEOF\n\n# Generate the certificate\nopenssl req -x509 -newkey rsa:4096 -sha256 -days 3650 \\\n    -nodes -keyout /tmp/codexbar-dev.key -out /tmp/codexbar-dev.crt \\\n    -config \"$TEMP_CONFIG\" 2>/dev/null\n\n# Convert to PKCS12 format\nopenssl pkcs12 -export -out /tmp/codexbar-dev.p12 \\\n    -inkey /tmp/codexbar-dev.key -in /tmp/codexbar-dev.crt \\\n    -passout pass: 2>/dev/null\n\n# Import into keychain\nsecurity import /tmp/codexbar-dev.p12 -k ~/Library/Keychains/login.keychain-db -T /usr/bin/codesign -T /usr/bin/security\n\n# Clean up temporary files\nrm -f /tmp/codexbar-dev.{key,crt,p12}\n\necho \"\"\necho \"✅ Certificate created successfully!\"\necho \"\"\necho \"⚠️  IMPORTANT: You need to trust this certificate for code signing:\"\necho \"\"\necho \"1. Open Keychain Access.app\"\necho \"2. Find '$CERT_NAME' in the 'login' keychain\"\necho \"3. Double-click it\"\necho \"4. Expand 'Trust' section\"\necho \"5. Set 'Code Signing' to 'Always Trust'\"\necho \"6. Close the window (enter your password when prompted)\"\necho \"\"\necho \"Then add this to your shell profile (~/.zshrc or ~/.bashrc):\"\necho \"\"\necho \"    export APP_IDENTITY='$CERT_NAME'\"\necho \"\"\necho \"Restart your terminal and rebuild with ./Scripts/compile_and_run.sh\"\n\n"
  },
  {
    "path": "Scripts/sign-and-notarize.sh",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\nAPP_NAME=\"CodexBar\"\nAPP_IDENTITY=\"Developer ID Application: Peter Steinberger (Y5PE65HELJ)\"\nAPP_BUNDLE=\"CodexBar.app\"\nROOT=$(cd \"$(dirname \"$0\")/..\" && pwd)\nsource \"$ROOT/version.env\"\nZIP_NAME=\"${APP_NAME}-${MARKETING_VERSION}.zip\"\nDSYM_ZIP=\"${APP_NAME}-${MARKETING_VERSION}.dSYM.zip\"\n\nif [[ -z \"${APP_STORE_CONNECT_API_KEY_P8:-}\" || -z \"${APP_STORE_CONNECT_KEY_ID:-}\" || -z \"${APP_STORE_CONNECT_ISSUER_ID:-}\" ]]; then\n  echo \"Missing APP_STORE_CONNECT_* env vars (API key, key id, issuer id).\" >&2\n  exit 1\nfi\nif [[ -z \"${SPARKLE_PRIVATE_KEY_FILE:-}\" ]]; then\n  echo \"SPARKLE_PRIVATE_KEY_FILE is required for release signing/verification.\" >&2\n  exit 1\nfi\nif [[ ! -f \"$SPARKLE_PRIVATE_KEY_FILE\" ]]; then\n  echo \"Sparkle key file not found: $SPARKLE_PRIVATE_KEY_FILE\" >&2\n  exit 1\nfi\nkey_lines=$(grep -v '^[[:space:]]*#' \"$SPARKLE_PRIVATE_KEY_FILE\" | sed '/^[[:space:]]*$/d')\nif [[ $(printf \"%s\\n\" \"$key_lines\" | wc -l) -ne 1 ]]; then\n  echo \"Sparkle key file must contain exactly one base64 line (no comments/blank lines).\" >&2\n  exit 1\nfi\n\necho \"$APP_STORE_CONNECT_API_KEY_P8\" | sed 's/\\\\n/\\n/g' > /tmp/codexbar-api-key.p8\ntrap 'rm -f /tmp/codexbar-api-key.p8 /tmp/${APP_NAME}Notarize.zip' EXIT\n\n# Allow building a universal binary if ARCHES is provided; default to universal (arm64 + x86_64).\nARCHES_VALUE=${ARCHES:-\"arm64 x86_64\"}\nARCH_LIST=( ${ARCHES_VALUE} )\nfor ARCH in \"${ARCH_LIST[@]}\"; do\n  swift build -c release --arch \"$ARCH\"\ndone\nARCHES=\"${ARCHES_VALUE}\" ./Scripts/package_app.sh release\n\nENTITLEMENTS_DIR=\"$ROOT/.build/entitlements\"\nAPP_ENTITLEMENTS=\"${ENTITLEMENTS_DIR}/CodexBar.entitlements\"\nWIDGET_ENTITLEMENTS=\"${ENTITLEMENTS_DIR}/CodexBarWidget.entitlements\"\n\necho \"Signing with $APP_IDENTITY\"\nif [[ -f \"$APP_BUNDLE/Contents/Helpers/CodexBarCLI\" ]]; then\n  codesign --force --timestamp --options runtime --sign \"$APP_IDENTITY\" \\\n    \"$APP_BUNDLE/Contents/Helpers/CodexBarCLI\"\nfi\nif [[ -f \"$APP_BUNDLE/Contents/Helpers/CodexBarClaudeWatchdog\" ]]; then\n  codesign --force --timestamp --options runtime --sign \"$APP_IDENTITY\" \\\n    \"$APP_BUNDLE/Contents/Helpers/CodexBarClaudeWatchdog\"\nfi\nif [[ -d \"$APP_BUNDLE/Contents/PlugIns/CodexBarWidget.appex\" ]]; then\n  codesign --force --timestamp --options runtime --sign \"$APP_IDENTITY\" \\\n    --entitlements \"$WIDGET_ENTITLEMENTS\" \\\n    \"$APP_BUNDLE/Contents/PlugIns/CodexBarWidget.appex/Contents/MacOS/CodexBarWidget\"\n  codesign --force --timestamp --options runtime --sign \"$APP_IDENTITY\" \\\n    --entitlements \"$WIDGET_ENTITLEMENTS\" \\\n    \"$APP_BUNDLE/Contents/PlugIns/CodexBarWidget.appex\"\nfi\ncodesign --force --timestamp --options runtime --sign \"$APP_IDENTITY\" \\\n  --entitlements \"$APP_ENTITLEMENTS\" \\\n  \"$APP_BUNDLE\"\n\nDITTO_BIN=${DITTO_BIN:-/usr/bin/ditto}\n\"$DITTO_BIN\" --norsrc -c -k --keepParent \"$APP_BUNDLE\" \"/tmp/${APP_NAME}Notarize.zip\"\n\necho \"Submitting for notarization\"\nxcrun notarytool submit \"/tmp/${APP_NAME}Notarize.zip\" \\\n  --key /tmp/codexbar-api-key.p8 \\\n  --key-id \"$APP_STORE_CONNECT_KEY_ID\" \\\n  --issuer \"$APP_STORE_CONNECT_ISSUER_ID\" \\\n  --wait\n\necho \"Stapling ticket\"\nxcrun stapler staple \"$APP_BUNDLE\"\n\n# Strip any extended attributes that would create AppleDouble files when zipping\nxattr -cr \"$APP_BUNDLE\"\nfind \"$APP_BUNDLE\" -name '._*' -delete\n\n\"$DITTO_BIN\" --norsrc -c -k --keepParent \"$APP_BUNDLE\" \"$ZIP_NAME\"\n\nspctl -a -t exec -vv \"$APP_BUNDLE\"\nstapler validate \"$APP_BUNDLE\"\n\necho \"Packaging dSYM\"\nFIRST_ARCH=\"${ARCH_LIST[0]}\"\nPREFERRED_ARCH_DIR=\".build/${FIRST_ARCH}-apple-macosx/release\"\nDSYM_PATH=\"${PREFERRED_ARCH_DIR}/${APP_NAME}.dSYM\"\nif [[ ! -d \"$DSYM_PATH\" ]]; then\n  echo \"Missing dSYM at $DSYM_PATH\" >&2\n  exit 1\nfi\nif [[ ${#ARCH_LIST[@]} -gt 1 ]]; then\n  MERGED_DSYM=\"${PREFERRED_ARCH_DIR}/${APP_NAME}.dSYM-universal\"\n  rm -rf \"$MERGED_DSYM\"\n  cp -R \"$DSYM_PATH\" \"$MERGED_DSYM\"\n  DWARF_PATH=\"${MERGED_DSYM}/Contents/Resources/DWARF/${APP_NAME}\"\n  BINARIES=()\n  for ARCH in \"${ARCH_LIST[@]}\"; do\n    ARCH_DSYM=\".build/${ARCH}-apple-macosx/release/${APP_NAME}.dSYM/Contents/Resources/DWARF/${APP_NAME}\"\n    if [[ ! -f \"$ARCH_DSYM\" ]]; then\n      echo \"Missing dSYM for ${ARCH} at $ARCH_DSYM\" >&2\n      exit 1\n    fi\n    BINARIES+=(\"$ARCH_DSYM\")\n  done\n  lipo -create \"${BINARIES[@]}\" -output \"$DWARF_PATH\"\n  DSYM_PATH=\"$MERGED_DSYM\"\nfi\n\"$DITTO_BIN\" --norsrc -c -k --keepParent \"$DSYM_PATH\" \"$DSYM_ZIP\"\n\necho \"Done: $ZIP_NAME\"\n"
  },
  {
    "path": "Scripts/test_live_update.sh",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\nPREV_TAG=${1:?\"pass previous release tag (e.g. v0.1.0)\"}\nCUR_TAG=${2:?\"pass current release tag (e.g. v0.1.1)\"}\n\nROOT=$(cd \"$(dirname \"$0\")/..\" && pwd)\nPREV_VER=${PREV_TAG#v}\nAPP_NAME=\"CodexBar\"\n\nZIP_URL=\"https://github.com/steipete/CodexBar/releases/download/${PREV_TAG}/${APP_NAME}-${PREV_VER}.zip\"\nTMP_DIR=$(mktemp -d /tmp/codexbar-live.XXXX)\ntrap 'rm -rf \"$TMP_DIR\"' EXIT\n\necho \"Downloading previous release $PREV_TAG from $ZIP_URL\"\ncurl -L -o \"$TMP_DIR/prev.zip\" \"$ZIP_URL\"\n\necho \"Installing previous release to /Applications/${APP_NAME}.app\"\nrm -rf /Applications/${APP_NAME}.app\nditto -x -k \"$TMP_DIR/prev.zip\" \"$TMP_DIR\"\nditto \"$TMP_DIR/${APP_NAME}.app\" /Applications/${APP_NAME}.app\n\necho \"Launching previous build…\"\nopen -n /Applications/${APP_NAME}.app\nsleep 4\n\ncat <<'MSG'\nManual step: trigger \"Check for Updates…\" in the app and install the update.\nExpect to land on the newly released version. When done, confirm below.\nMSG\n\nread -rp \"Did the update succeed from ${PREV_TAG} to ${CUR_TAG}? (y/N) \" answer\nif [[ ! \"$answer\" =~ ^[Yy]$ ]]; then\n  echo \"Live update test NOT confirmed; failing per RUN_SPARKLE_UPDATE_TEST.\" >&2\n  exit 1\nfi\n\necho \"Live update test confirmed.\"\n"
  },
  {
    "path": "Scripts/validate_changelog.sh",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\nVERSION=${1:?\"usage: $0 <version>\"}\nROOT=$(cd \"$(dirname \"$0\")/..\" && pwd)\ncd \"$ROOT\"\n\nfirst_line=$(grep -m1 '^## ' CHANGELOG.md | sed 's/^## //')\nif [[ \"$first_line\" != ${VERSION}* ]]; then\n  echo \"ERROR: Top CHANGELOG section is '$first_line' but expected '${VERSION} — …'\" >&2\n  exit 1\nfi\n\ngrep -q \"^## ${VERSION} \" CHANGELOG.md || {\n  echo \"ERROR: No section for version ${VERSION} in CHANGELOG.md\" >&2\n  exit 1\n}\n\ngrep -q '^## [0-9]\\+\\.[0-9]\\+\\.[0-9].*Unreleased' CHANGELOG.md && {\n  echo \"ERROR: Top section still labeled Unreleased; finalize changelog first.\" >&2\n  exit 1\n}\n\necho \"Changelog OK for ${VERSION}\"\n"
  },
  {
    "path": "Scripts/verify_appcast.sh",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\n# Verifies that the appcast entry for the given version has a valid ed25519 signature\n# and that the enclosure length matches the downloaded archive.\n#\n# Usage: SPARKLE_PRIVATE_KEY_FILE=/path/to/key ./Scripts/verify_appcast.sh [version]\n\nROOT=$(cd \"$(dirname \"$0\")/..\" && pwd)\nVERSION=${1:-$(source \"$ROOT/version.env\" && echo \"$MARKETING_VERSION\")}\nAPPCAST=\"${ROOT}/appcast.xml\"\n\nif [[ -z \"${SPARKLE_PRIVATE_KEY_FILE:-}\" ]]; then\n  echo \"SPARKLE_PRIVATE_KEY_FILE is required\" >&2\n  exit 1\nfi\nif [[ ! -f \"$SPARKLE_PRIVATE_KEY_FILE\" ]]; then\n  echo \"Sparkle key file not found: $SPARKLE_PRIVATE_KEY_FILE\" >&2\n  exit 1\nfi\nif [[ ! -f \"$APPCAST\" ]]; then\n  echo \"appcast.xml not found at $APPCAST\" >&2\n  exit 1\nfi\n\n# Clean the key file: strip comments/blank lines and require exactly one line of base64.\nfunction cleaned_key_path() {\n  local tmp key_lines\n  key_lines=$(grep -v '^[[:space:]]*#' \"$SPARKLE_PRIVATE_KEY_FILE\" | sed '/^[[:space:]]*$/d')\n  if [[ $(printf \"%s\\n\" \"$key_lines\" | wc -l) -ne 1 ]]; then\n    echo \"Sparkle key file must contain exactly one base64 line (no comments/blank lines).\" >&2\n    exit 1\n  fi\n  tmp=$(mktemp)\n  printf \"%s\" \"$key_lines\" > \"$tmp\"\n  echo \"$tmp\"\n}\n\nKEY_FILE=$(cleaned_key_path)\ntrap 'rm -f \"$KEY_FILE\" \"$TMP_ZIP\"' EXIT\n\nTMP_ZIP=$(mktemp /tmp/codexbar-enclosure.XXXX.zip)\n\npython3 - \"$APPCAST\" \"$VERSION\" >\"$TMP_ZIP.meta\" <<'PY'\nimport sys, xml.etree.ElementTree as ET\n\nappcast = sys.argv[1]\nversion = sys.argv[2]\ntree = ET.parse(appcast)\nroot = tree.getroot()\nns = {\"sparkle\": \"http://www.andymatuschak.org/xml-namespaces/sparkle\"}\n\nentry = None\nfor item in root.findall(\"./channel/item\"):\n    sv = item.findtext(\"sparkle:shortVersionString\", default=\"\", namespaces=ns)\n    if sv == version:\n        entry = item\n        break\n\nif entry is None:\n    sys.exit(\"No appcast entry found for version {}\".format(version))\n\nenclosure = entry.find(\"enclosure\")\nurl = enclosure.get(\"url\")\nsig = enclosure.get(\"{http://www.andymatuschak.org/xml-namespaces/sparkle}edSignature\")\nlength = enclosure.get(\"length\")\n\nif not all([url, sig, length]):\n    sys.exit(\"Missing url/signature/length in appcast for version {}\".format(version))\n\nprint(url)\nprint(sig)\nprint(length)\nPY\n\nreadarray -t META <\"$TMP_ZIP.meta\"\nURL=\"${META[0]}\"\nSIG=\"${META[1]}\"\nLEN_EXPECTED=\"${META[2]}\"\n\necho \"Downloading enclosure: $URL\"\ncurl -L -o \"$TMP_ZIP\" \"$URL\"\n\nLEN_ACTUAL=$(stat -f%z \"$TMP_ZIP\")\nif [[ \"$LEN_ACTUAL\" != \"$LEN_EXPECTED\" ]]; then\n  echo \"Length mismatch: expected $LEN_EXPECTED, got $LEN_ACTUAL\" >&2\n  exit 1\nfi\n\necho \"Verifying Sparkle signature…\"\nsign_update --verify \"$TMP_ZIP\" \"$SIG\" --ed-key-file \"$KEY_FILE\"\necho \"Appcast entry for $VERSION verified (signature and length match).\"\n"
  },
  {
    "path": "Sources/CodexBar/About.swift",
    "content": "import AppKit\n\n@MainActor\nfunc showAbout() {\n    NSApp.activate(ignoringOtherApps: true)\n\n    let version = Bundle.main.object(forInfoDictionaryKey: \"CFBundleShortVersionString\") as? String ?? \"–\"\n    let build = Bundle.main.object(forInfoDictionaryKey: \"CFBundleVersion\") as? String ?? \"\"\n    let versionString = build.isEmpty ? version : \"\\(version) (\\(build))\"\n    let buildTimestamp = Bundle.main.object(forInfoDictionaryKey: \"CodexBuildTimestamp\") as? String\n    let gitCommit = Bundle.main.object(forInfoDictionaryKey: \"CodexGitCommit\") as? String\n\n    let separator = NSAttributedString(string: \" · \", attributes: [\n        .font: NSFont.systemFont(ofSize: NSFont.smallSystemFontSize),\n    ])\n\n    func makeLink(_ title: String, urlString: String) -> NSAttributedString {\n        NSAttributedString(string: title, attributes: [\n            .link: URL(string: urlString) as Any,\n            .font: NSFont.systemFont(ofSize: NSFont.smallSystemFontSize),\n        ])\n    }\n\n    let credits = NSMutableAttributedString(string: \"Peter Steinberger — MIT License\\n\")\n    credits.append(makeLink(\"GitHub\", urlString: \"https://github.com/steipete/CodexBar\"))\n    credits.append(separator)\n    credits.append(makeLink(\"Website\", urlString: \"https://codexbar.app\"))\n    credits.append(separator)\n    credits.append(makeLink(\"Twitter\", urlString: \"https://twitter.com/steipete\"))\n    credits.append(separator)\n    credits.append(makeLink(\"Email\", urlString: \"mailto:peter@steipete.me\"))\n    if let buildTimestamp, let formatted = formattedBuildTimestamp(buildTimestamp) {\n        var builtLine = \"Built \\(formatted)\"\n        if let gitCommit, !gitCommit.isEmpty, gitCommit != \"unknown\" {\n            builtLine += \" (\\(gitCommit)\"\n            #if DEBUG\n            builtLine += \" DEBUG BUILD\"\n            #endif\n            builtLine += \")\"\n        }\n        credits.append(NSAttributedString(string: \"\\n\\(builtLine)\", attributes: [\n            .font: NSFont.systemFont(ofSize: NSFont.smallSystemFontSize),\n            .foregroundColor: NSColor.secondaryLabelColor,\n        ]))\n    }\n\n    let options: [NSApplication.AboutPanelOptionKey: Any] = [\n        .applicationName: \"CodexBar\",\n        .applicationVersion: versionString,\n        .version: versionString,\n        .credits: credits,\n        .applicationIcon: (NSApplication.shared.applicationIconImage ?? NSImage()) as Any,\n    ]\n\n    NSApp.orderFrontStandardAboutPanel(options: options)\n\n    // Remove the focus ring around the app icon in the standard About panel for a cleaner look.\n    if let aboutPanel = NSApp.windows.first(where: { $0.className.contains(\"About\") }) {\n        removeFocusRings(in: aboutPanel.contentView)\n    }\n}\n\nprivate func formattedBuildTimestamp(_ timestamp: String) -> String? {\n    let parser = ISO8601DateFormatter()\n    parser.formatOptions = [.withInternetDateTime]\n    guard let date = parser.date(from: timestamp) else { return timestamp }\n\n    let formatter = DateFormatter()\n    formatter.dateStyle = .medium\n    formatter.timeStyle = .short\n    formatter.locale = .current\n    return formatter.string(from: date)\n}\n\n@MainActor\nprivate func removeFocusRings(in view: NSView?) {\n    guard let view else { return }\n    if let imageView = view as? NSImageView {\n        imageView.focusRingType = .none\n    }\n    for subview in view.subviews {\n        removeFocusRings(in: subview)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/AppNotifications.swift",
    "content": "import CodexBarCore\nimport Foundation\n@preconcurrency import UserNotifications\n\n@MainActor\nfinal class AppNotifications {\n    static let shared = AppNotifications()\n\n    private let centerProvider: @Sendable () -> UNUserNotificationCenter\n    private let logger = CodexBarLog.logger(LogCategories.notifications)\n    private var authorizationTask: Task<Bool, Never>?\n\n    init(centerProvider: @escaping @Sendable () -> UNUserNotificationCenter = { UNUserNotificationCenter.current() }) {\n        self.centerProvider = centerProvider\n    }\n\n    func requestAuthorizationOnStartup() {\n        guard !Self.isRunningUnderTests else { return }\n        _ = self.ensureAuthorizationTask()\n    }\n\n    func post(idPrefix: String, title: String, body: String, badge: NSNumber? = nil) {\n        guard !Self.isRunningUnderTests else { return }\n        let center = self.centerProvider()\n        let logger = self.logger\n\n        Task { @MainActor in\n            let granted = await self.ensureAuthorized()\n            guard granted else {\n                logger.debug(\"not authorized; skipping post\", metadata: [\"prefix\": idPrefix])\n                return\n            }\n\n            let content = UNMutableNotificationContent()\n            content.title = title\n            content.body = body\n            content.sound = .default\n            content.badge = badge\n\n            let request = UNNotificationRequest(\n                identifier: \"codexbar-\\(idPrefix)-\\(UUID().uuidString)\",\n                content: content,\n                trigger: nil)\n\n            logger.info(\"posting\", metadata: [\"prefix\": idPrefix])\n            do {\n                try await center.add(request)\n            } catch {\n                let errorText = String(describing: error)\n                logger.error(\"failed to post\", metadata: [\"prefix\": idPrefix, \"error\": errorText])\n            }\n        }\n    }\n\n    // MARK: - Private\n\n    private func ensureAuthorizationTask() -> Task<Bool, Never> {\n        if let authorizationTask { return authorizationTask }\n        let task = Task { @MainActor in\n            await self.requestAuthorization()\n        }\n        self.authorizationTask = task\n        return task\n    }\n\n    private func ensureAuthorized() async -> Bool {\n        await self.ensureAuthorizationTask().value\n    }\n\n    private func requestAuthorization() async -> Bool {\n        if let existing = await self.notificationAuthorizationStatus() {\n            if existing == .authorized || existing == .provisional {\n                return true\n            }\n            if existing == .denied {\n                return false\n            }\n        }\n\n        let center = self.centerProvider()\n        return await withCheckedContinuation { continuation in\n            center.requestAuthorization(options: [.alert, .sound, .badge]) { granted, _ in\n                continuation.resume(returning: granted)\n            }\n        }\n    }\n\n    private func notificationAuthorizationStatus() async -> UNAuthorizationStatus? {\n        let center = self.centerProvider()\n        return await withCheckedContinuation { continuation in\n            center.getNotificationSettings { settings in\n                continuation.resume(returning: settings.authorizationStatus)\n            }\n        }\n    }\n\n    private static var isRunningUnderTests: Bool {\n        // Swift Testing doesn't always set XCTest env vars, and removing XCTest imports from\n        // the test target can make NSClassFromString(\"XCTestCase\") return nil. If we're not\n        // running inside an app bundle, treat it as \"tests/headless\" to avoid crashes when\n        // accessing UNUserNotificationCenter.\n        if Bundle.main.bundleURL.pathExtension != \"app\" { return true }\n        let env = ProcessInfo.processInfo.environment\n        if env[\"XCTestConfigurationFilePath\"] != nil { return true }\n        if env[\"TESTING_LIBRARY_VERSION\"] != nil { return true }\n        if env[\"SWIFT_TESTING\"] != nil { return true }\n        return NSClassFromString(\"XCTestCase\") != nil\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/ClaudeLoginRunner.swift",
    "content": "import CodexBarCore\nimport Darwin\nimport Foundation\n\nstruct ClaudeLoginRunner {\n    enum Phase {\n        case requesting\n        case waitingBrowser\n    }\n\n    struct Result {\n        enum Outcome {\n            case success\n            case timedOut\n            case failed(status: Int32)\n            case missingBinary\n            case launchFailed(String)\n        }\n\n        let outcome: Outcome\n        let output: String\n        let authLink: String?\n    }\n\n    static func run(timeout: TimeInterval = 120, onPhaseChange: @escaping @Sendable (Phase) -> Void) async -> Result {\n        await Task(priority: .userInitiated) {\n            onPhaseChange(.requesting)\n            do {\n                let runResult = try self.runPTY(timeout: timeout, onPhaseChange: onPhaseChange)\n                let link = self.firstLink(in: runResult.output)\n                if let link {\n                    return Result(outcome: .success, output: runResult.output, authLink: link)\n                }\n                return Result(outcome: .timedOut, output: runResult.output, authLink: nil)\n            } catch LoginError.binaryNotFound {\n                return Result(outcome: .missingBinary, output: \"\", authLink: nil)\n            } catch let LoginError.timedOut(text) {\n                return Result(outcome: .timedOut, output: text, authLink: self.firstLink(in: text))\n            } catch let LoginError.failed(status, text) {\n                return Result(outcome: .failed(status: status), output: text, authLink: self.firstLink(in: text))\n            } catch {\n                return Result(outcome: .launchFailed(error.localizedDescription), output: \"\", authLink: nil)\n            }\n        }.value\n    }\n\n    // MARK: - PTY runner\n\n    private enum LoginError: Error {\n        case binaryNotFound\n        case timedOut(text: String)\n        case failed(status: Int32, text: String)\n        case launchFailed(String)\n    }\n\n    private struct PTYRunResult {\n        let output: String\n    }\n\n    private static func runPTY(\n        timeout: TimeInterval,\n        onPhaseChange: @escaping @Sendable (Phase) -> Void) throws -> PTYRunResult\n    {\n        let runner = TTYCommandRunner()\n        var options = TTYCommandRunner.Options(rows: 50, cols: 160, timeout: timeout)\n        options.extraArgs = [\"/login\"]\n        options.stopOnURL = false // keep running until CLI confirms\n        options.stopOnSubstrings = [\"Successfully logged in\", \"Login successful\", \"Logged in successfully\"]\n        options.sendEnterEvery = 1.0\n        options.settleAfterStop = 0.35\n        do {\n            let result = try runner.run(\n                binary: \"claude\",\n                send: \"\",\n                options: options,\n                onURLDetected: { onPhaseChange(.waitingBrowser) })\n            return PTYRunResult(output: result.text)\n        } catch TTYCommandRunner.Error.binaryNotFound {\n            throw LoginError.binaryNotFound\n        } catch TTYCommandRunner.Error.timedOut {\n            throw LoginError.timedOut(text: \"\")\n        } catch let TTYCommandRunner.Error.launchFailed(msg) {\n            throw LoginError.launchFailed(msg)\n        } catch {\n            throw LoginError.launchFailed(error.localizedDescription)\n        }\n    }\n\n    private static func firstLink(in text: String) -> String? {\n        let pattern = #\"https?://[A-Za-z0-9._~:/?#\\[\\]@!$&'()*+,;=%-]+\"#\n        guard let regex = try? NSRegularExpression(pattern: pattern) else { return nil }\n        let nsRange = NSRange(text.startIndex..<text.endIndex, in: text)\n        guard let match = regex.firstMatch(in: text, options: [], range: nsRange),\n              let range = Range(match.range, in: text) else { return nil }\n        var url = String(text[range])\n        while let last = url.unicodeScalars.last,\n              CharacterSet(charactersIn: \".,;:)]}>\\\"'\").contains(last)\n        {\n            url.unicodeScalars.removeLast()\n        }\n        return url\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/CodexLoginRunner.swift",
    "content": "import CodexBarCore\nimport Darwin\nimport Foundation\n\nstruct CodexLoginRunner {\n    struct Result {\n        enum Outcome {\n            case success\n            case timedOut\n            case failed(status: Int32)\n            case missingBinary\n            case launchFailed(String)\n        }\n\n        let outcome: Outcome\n        let output: String\n    }\n\n    static func run(timeout: TimeInterval = 120) async -> Result {\n        await Task(priority: .userInitiated) {\n            var env = ProcessInfo.processInfo.environment\n            env[\"PATH\"] = PathBuilder.effectivePATH(\n                purposes: [.rpc, .tty, .nodeTooling],\n                env: env,\n                loginPATH: LoginShellPathCache.shared.current)\n\n            guard let executable = BinaryLocator.resolveCodexBinary(\n                env: env,\n                loginPATH: LoginShellPathCache.shared.current)\n            else {\n                return Result(outcome: .missingBinary, output: \"\")\n            }\n\n            let process = Process()\n            process.executableURL = URL(fileURLWithPath: \"/usr/bin/env\")\n            process.arguments = [executable, \"login\"]\n            process.environment = env\n\n            let stdout = Pipe()\n            let stderr = Pipe()\n            process.standardOutput = stdout\n            process.standardError = stderr\n\n            var processGroup: pid_t?\n            do {\n                try process.run()\n                processGroup = self.attachProcessGroup(process)\n            } catch {\n                return Result(outcome: .launchFailed(error.localizedDescription), output: \"\")\n            }\n\n            let timedOut = await self.wait(for: process, timeout: timeout)\n            if timedOut {\n                self.terminate(process, processGroup: processGroup)\n            }\n\n            let output = await self.combinedOutput(stdout: stdout, stderr: stderr)\n            if timedOut {\n                return Result(outcome: .timedOut, output: output)\n            }\n\n            let status = process.terminationStatus\n            if status == 0 {\n                return Result(outcome: .success, output: output)\n            }\n            return Result(outcome: .failed(status: status), output: output)\n        }.value\n    }\n\n    private static func wait(for process: Process, timeout: TimeInterval) async -> Bool {\n        await withTaskGroup(of: Bool.self) { group -> Bool in\n            group.addTask {\n                process.waitUntilExit()\n                return false\n            }\n            group.addTask {\n                let nanos = UInt64(max(0, timeout) * 1_000_000_000)\n                try? await Task.sleep(nanoseconds: nanos)\n                return true\n            }\n            let result = await group.next() ?? false\n            group.cancelAll()\n            return result\n        }\n    }\n\n    private static func terminate(_ process: Process, processGroup: pid_t?) {\n        if let pgid = processGroup {\n            kill(-pgid, SIGTERM)\n        }\n        if process.isRunning {\n            process.terminate()\n        }\n\n        let deadline = Date().addingTimeInterval(2.0)\n        while process.isRunning, Date() < deadline {\n            usleep(100_000)\n        }\n\n        if process.isRunning {\n            if let pgid = processGroup {\n                kill(-pgid, SIGKILL)\n            }\n            kill(process.processIdentifier, SIGKILL)\n        }\n    }\n\n    private static func attachProcessGroup(_ process: Process) -> pid_t? {\n        let pid = process.processIdentifier\n        return setpgid(pid, pid) == 0 ? pid : nil\n    }\n\n    private static func combinedOutput(stdout: Pipe, stderr: Pipe) async -> String {\n        async let out = self.readToEnd(stdout)\n        async let err = self.readToEnd(stderr)\n        let stdoutText = await out\n        let stderrText = await err\n\n        let merged: String = if !stdoutText.isEmpty, !stderrText.isEmpty {\n            [stdoutText, stderrText].joined(separator: \"\\n\")\n        } else {\n            stdoutText + stderrText\n        }\n        let trimmed = merged.trimmingCharacters(in: .whitespacesAndNewlines)\n        let limited = trimmed.prefix(4000)\n        return limited.isEmpty ? \"No output captured.\" : String(limited)\n    }\n\n    private static func readToEnd(_ pipe: Pipe, timeout: TimeInterval = 3.0) async -> String {\n        await withTaskGroup(of: String?.self) { group -> String in\n            group.addTask {\n                if #available(macOS 13.0, *) {\n                    if let data = try? pipe.fileHandleForReading.readToEnd() { return self.decode(data) }\n                }\n                let data = pipe.fileHandleForReading.readDataToEndOfFile()\n                return Self.decode(data)\n            }\n            group.addTask {\n                let nanos = UInt64(max(0, timeout) * 1_000_000_000)\n                try? await Task.sleep(nanoseconds: nanos)\n                return nil\n            }\n            let result = await group.next()\n            group.cancelAll()\n            if let result, let text = result { return text }\n            return \"\"\n        }\n    }\n\n    private static func decode(_ data: Data) -> String {\n        guard let text = String(data: data, encoding: .utf8) else { return \"\" }\n        return text\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/CodexbarApp.swift",
    "content": "import AppKit\nimport CodexBarCore\nimport KeyboardShortcuts\nimport Observation\nimport QuartzCore\nimport Security\nimport SwiftUI\n\n@main\nstruct CodexBarApp: App {\n    @NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate\n    @State private var settings: SettingsStore\n    @State private var store: UsageStore\n    private let preferencesSelection: PreferencesSelection\n    private let account: AccountInfo\n\n    init() {\n        let env = ProcessInfo.processInfo.environment\n        let storedLevel = CodexBarLog.parseLevel(UserDefaults.standard.string(forKey: \"debugLogLevel\")) ?? .verbose\n        let level = CodexBarLog.parseLevel(env[\"CODEXBAR_LOG_LEVEL\"]) ?? storedLevel\n        CodexBarLog.bootstrapIfNeeded(.init(\n            destination: .oslog(subsystem: \"com.steipete.codexbar\"),\n            level: level,\n            json: false))\n\n        let version = Bundle.main.object(forInfoDictionaryKey: \"CFBundleShortVersionString\") as? String ?? \"unknown\"\n        let build = Bundle.main.object(forInfoDictionaryKey: \"CFBundleVersion\") as? String ?? \"unknown\"\n        let gitCommit = Bundle.main.object(forInfoDictionaryKey: \"CodexGitCommit\") as? String ?? \"unknown\"\n        let buildTimestamp = Bundle.main.object(forInfoDictionaryKey: \"CodexBuildTimestamp\") as? String ?? \"unknown\"\n        CodexBarLog.logger(LogCategories.app).info(\n            \"CodexBar starting\",\n            metadata: [\n                \"version\": version,\n                \"build\": build,\n                \"git\": gitCommit,\n                \"built\": buildTimestamp,\n            ])\n\n        KeychainAccessGate.isDisabled = UserDefaults.standard.bool(forKey: \"debugDisableKeychainAccess\")\n        KeychainPromptCoordinator.install()\n\n        let preferencesSelection = PreferencesSelection()\n        let settings = SettingsStore()\n        let fetcher = UsageFetcher()\n        let browserDetection = BrowserDetection(cacheTTL: BrowserDetection.defaultCacheTTL)\n        let account = fetcher.loadAccountInfo()\n        let store = UsageStore(fetcher: fetcher, browserDetection: browserDetection, settings: settings)\n        self.preferencesSelection = preferencesSelection\n        _settings = State(wrappedValue: settings)\n        _store = State(wrappedValue: store)\n        self.account = account\n        CodexBarLog.setLogLevel(settings.debugLogLevel)\n        self.appDelegate.configure(\n            store: store,\n            settings: settings,\n            account: account,\n            selection: preferencesSelection)\n    }\n\n    @SceneBuilder\n    var body: some Scene {\n        // Hidden 1×1 window to keep SwiftUI's lifecycle alive so `Settings` scene\n        // shows the native toolbar tabs even though the UI is AppKit-based.\n        WindowGroup(\"CodexBarLifecycleKeepalive\") {\n            HiddenWindowView()\n        }\n        .defaultSize(width: 20, height: 20)\n        .windowStyle(.hiddenTitleBar)\n\n        Settings {\n            PreferencesView(\n                settings: self.settings,\n                store: self.store,\n                updater: self.appDelegate.updaterController,\n                selection: self.preferencesSelection)\n        }\n        .defaultSize(width: PreferencesTab.general.preferredWidth, height: PreferencesTab.general.preferredHeight)\n        .windowResizability(.contentSize)\n    }\n\n    private func openSettings(tab: PreferencesTab) {\n        self.preferencesSelection.tab = tab\n        NSApp.activate(ignoringOtherApps: true)\n        _ = NSApp.sendAction(Selector((\"showPreferencesWindow:\")), to: nil, from: nil)\n    }\n}\n\n// MARK: - Updater abstraction\n\n@MainActor\nprotocol UpdaterProviding: AnyObject {\n    var automaticallyChecksForUpdates: Bool { get set }\n    var automaticallyDownloadsUpdates: Bool { get set }\n    var isAvailable: Bool { get }\n    var unavailableReason: String? { get }\n    var updateStatus: UpdateStatus { get }\n    func checkForUpdates(_ sender: Any?)\n}\n\n/// No-op updater used for debug builds and non-bundled runs to suppress Sparkle dialogs.\nfinal class DisabledUpdaterController: UpdaterProviding {\n    var automaticallyChecksForUpdates: Bool = false\n    var automaticallyDownloadsUpdates: Bool = false\n    let isAvailable: Bool = false\n    let unavailableReason: String?\n    let updateStatus = UpdateStatus()\n\n    init(unavailableReason: String? = nil) {\n        self.unavailableReason = unavailableReason\n    }\n\n    func checkForUpdates(_ sender: Any?) {}\n}\n\n@MainActor\n@Observable\nfinal class UpdateStatus {\n    static let disabled = UpdateStatus()\n    var isUpdateReady: Bool\n\n    init(isUpdateReady: Bool = false) {\n        self.isUpdateReady = isUpdateReady\n    }\n}\n\n#if canImport(Sparkle) && ENABLE_SPARKLE\nimport Sparkle\n\n@MainActor\nfinal class SparkleUpdaterController: NSObject, UpdaterProviding, SPUUpdaterDelegate {\n    private lazy var controller = SPUStandardUpdaterController(\n        startingUpdater: false,\n        updaterDelegate: self,\n        userDriverDelegate: nil)\n    let updateStatus = UpdateStatus()\n    let unavailableReason: String? = nil\n\n    init(savedAutoUpdate: Bool) {\n        super.init()\n        let updater = self.controller.updater\n        updater.automaticallyChecksForUpdates = savedAutoUpdate\n        updater.automaticallyDownloadsUpdates = savedAutoUpdate\n        self.controller.startUpdater()\n    }\n\n    var automaticallyChecksForUpdates: Bool {\n        get { self.controller.updater.automaticallyChecksForUpdates }\n        set { self.controller.updater.automaticallyChecksForUpdates = newValue }\n    }\n\n    var automaticallyDownloadsUpdates: Bool {\n        get { self.controller.updater.automaticallyDownloadsUpdates }\n        set { self.controller.updater.automaticallyDownloadsUpdates = newValue }\n    }\n\n    var isAvailable: Bool {\n        true\n    }\n\n    func checkForUpdates(_ sender: Any?) {\n        self.controller.checkForUpdates(sender)\n    }\n\n    nonisolated func updater(_ updater: SPUUpdater, didDownloadUpdate item: SUAppcastItem) {\n        Task { @MainActor in\n            self.updateStatus.isUpdateReady = true\n        }\n    }\n\n    nonisolated func updater(_ updater: SPUUpdater, failedToDownloadUpdate item: SUAppcastItem, error: Error) {\n        Task { @MainActor in\n            self.updateStatus.isUpdateReady = false\n        }\n    }\n\n    nonisolated func userDidCancelDownload(_ updater: SPUUpdater) {\n        Task { @MainActor in\n            self.updateStatus.isUpdateReady = false\n        }\n    }\n\n    nonisolated func updater(\n        _ updater: SPUUpdater,\n        userDidMake choice: SPUUserUpdateChoice,\n        forUpdate updateItem: SUAppcastItem,\n        state: SPUUserUpdateState)\n    {\n        let downloaded = state.stage == .downloaded\n        Task { @MainActor in\n            switch choice {\n            case .install, .skip:\n                self.updateStatus.isUpdateReady = false\n            case .dismiss:\n                self.updateStatus.isUpdateReady = downloaded\n            @unknown default:\n                self.updateStatus.isUpdateReady = false\n            }\n        }\n    }\n\n    nonisolated func allowedChannels(for updater: SPUUpdater) -> Set<String> {\n        UpdateChannel.current.allowedSparkleChannels\n    }\n}\n\nprivate func isDeveloperIDSigned(bundleURL: URL) -> Bool {\n    var staticCode: SecStaticCode?\n    guard SecStaticCodeCreateWithPath(bundleURL as CFURL, SecCSFlags(), &staticCode) == errSecSuccess,\n          let code = staticCode else { return false }\n\n    var infoCF: CFDictionary?\n    guard SecCodeCopySigningInformation(code, SecCSFlags(rawValue: kSecCSSigningInformation), &infoCF) == errSecSuccess,\n          let info = infoCF as? [String: Any],\n          let certs = info[kSecCodeInfoCertificates as String] as? [SecCertificate],\n          let leaf = certs.first else { return false }\n\n    if let summary = SecCertificateCopySubjectSummary(leaf) as String? {\n        return summary.hasPrefix(\"Developer ID Application:\")\n    }\n    return false\n}\n\n@MainActor\nprivate func makeUpdaterController() -> UpdaterProviding {\n    let bundleURL = Bundle.main.bundleURL\n    let isBundledApp = bundleURL.pathExtension == \"app\"\n    guard isBundledApp else {\n        return DisabledUpdaterController(unavailableReason: \"Updates unavailable in this build.\")\n    }\n\n    if InstallOrigin.isHomebrewCask(appBundleURL: bundleURL) {\n        return DisabledUpdaterController(\n            unavailableReason: \"Updates managed by Homebrew. Run: brew upgrade --cask steipete/tap/codexbar\")\n    }\n\n    guard isDeveloperIDSigned(bundleURL: bundleURL) else {\n        return DisabledUpdaterController(unavailableReason: \"Updates unavailable in this build.\")\n    }\n\n    let defaults = UserDefaults.standard\n    let autoUpdateKey = \"autoUpdateEnabled\"\n    // Default to true for first launch; fall back to saved preference thereafter.\n    let savedAutoUpdate = (defaults.object(forKey: autoUpdateKey) as? Bool) ?? true\n    return SparkleUpdaterController(savedAutoUpdate: savedAutoUpdate)\n}\n#else\nprivate func makeUpdaterController() -> UpdaterProviding {\n    DisabledUpdaterController()\n}\n#endif\n\n@MainActor\nfinal class AppDelegate: NSObject, NSApplicationDelegate {\n    let updaterController: UpdaterProviding = makeUpdaterController()\n    private var statusController: StatusItemControlling?\n    private var store: UsageStore?\n    private var settings: SettingsStore?\n    private var account: AccountInfo?\n    private var preferencesSelection: PreferencesSelection?\n\n    func configure(store: UsageStore, settings: SettingsStore, account: AccountInfo, selection: PreferencesSelection) {\n        self.store = store\n        self.settings = settings\n        self.account = account\n        self.preferencesSelection = selection\n    }\n\n    func applicationWillFinishLaunching(_ notification: Notification) {\n        self.configureAppIconForMacOSVersion()\n    }\n\n    func applicationDidFinishLaunching(_ notification: Notification) {\n        AppNotifications.shared.requestAuthorizationOnStartup()\n        self.ensureStatusController()\n        KeyboardShortcuts.onKeyUp(for: .openMenu) { [weak self] in\n            Task { @MainActor [weak self] in\n                self?.statusController?.openMenuFromShortcut()\n            }\n        }\n    }\n\n    func applicationWillTerminate(_ notification: Notification) {\n        TTYCommandRunner.terminateActiveProcessesForAppShutdown()\n    }\n\n    /// Use the classic (non-Liquid Glass) app icon on macOS versions before 26.\n    private func configureAppIconForMacOSVersion() {\n        if #unavailable(macOS 26) {\n            self.applyClassicAppIcon()\n        }\n    }\n\n    private func applyClassicAppIcon() {\n        guard let classicIcon = Self.loadClassicIcon() else { return }\n        NSApp.applicationIconImage = classicIcon\n    }\n\n    private static func loadClassicIcon() -> NSImage? {\n        guard let url = self.classicIconURL(),\n              let image = NSImage(contentsOf: url)\n        else {\n            return nil\n        }\n        return image\n    }\n\n    private static func classicIconURL() -> URL? {\n        Bundle.main.url(forResource: \"Icon-classic\", withExtension: \"icns\")\n    }\n\n    private func ensureStatusController() {\n        if self.statusController != nil { return }\n\n        if let store, let settings, let account, let selection = self.preferencesSelection {\n            self.statusController = StatusItemController.factory(\n                store,\n                settings,\n                account,\n                self.updaterController,\n                selection)\n            return\n        }\n\n        // Defensive fallback: this should not be hit in normal app lifecycle.\n        CodexBarLog.logger(LogCategories.app)\n            .error(\"StatusItemController fallback path used; settings/store mismatch likely.\")\n        assertionFailure(\"StatusItemController fallback path used; check app lifecycle wiring.\")\n        let fallbackSettings = SettingsStore()\n        let fetcher = UsageFetcher()\n        let browserDetection = BrowserDetection(cacheTTL: BrowserDetection.defaultCacheTTL)\n        let fallbackAccount = fetcher.loadAccountInfo()\n        let fallbackStore = UsageStore(fetcher: fetcher, browserDetection: browserDetection, settings: fallbackSettings)\n        self.statusController = StatusItemController.factory(\n            fallbackStore,\n            fallbackSettings,\n            fallbackAccount,\n            self.updaterController,\n            PreferencesSelection())\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Config/CodexBarConfigMigrator.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nstruct CodexBarConfigMigrator {\n    struct LegacyStores {\n        let zaiTokenStore: any ZaiTokenStoring\n        let syntheticTokenStore: any SyntheticTokenStoring\n        let codexCookieStore: any CookieHeaderStoring\n        let claudeCookieStore: any CookieHeaderStoring\n        let cursorCookieStore: any CookieHeaderStoring\n        let opencodeCookieStore: any CookieHeaderStoring\n        let factoryCookieStore: any CookieHeaderStoring\n        let minimaxCookieStore: any MiniMaxCookieStoring\n        let minimaxAPITokenStore: any MiniMaxAPITokenStoring\n        let kimiTokenStore: any KimiTokenStoring\n        let kimiK2TokenStore: any KimiK2TokenStoring\n        let augmentCookieStore: any CookieHeaderStoring\n        let ampCookieStore: any CookieHeaderStoring\n        let copilotTokenStore: any CopilotTokenStoring\n        let tokenAccountStore: any ProviderTokenAccountStoring\n    }\n\n    private struct MigrationState {\n        var didUpdate = false\n        var sawLegacySecrets = false\n        var sawLegacyAccounts = false\n    }\n\n    static func loadOrMigrate(\n        configStore: CodexBarConfigStore,\n        userDefaults: UserDefaults,\n        stores: LegacyStores) -> CodexBarConfig\n    {\n        let log = CodexBarLog.logger(LogCategories.configMigration)\n        let existing = try? configStore.load()\n        var config = (existing ?? CodexBarConfig.makeDefault()).normalized()\n        var state = MigrationState()\n\n        if existing == nil {\n            self.applyLegacyOrderAndToggles(userDefaults: userDefaults, config: &config, state: &state)\n        }\n\n        self.applyLegacyCookieSources(userDefaults: userDefaults, config: &config, state: &state)\n        self.migrateLegacySecrets(userDefaults: userDefaults, stores: stores, config: &config, state: &state)\n        self.migrateLegacyAccounts(stores: stores, config: &config, state: &state)\n\n        if state.didUpdate {\n            do {\n                try configStore.save(config)\n            } catch {\n                log.error(\"Failed to persist config: \\(error)\")\n            }\n        }\n\n        if state.sawLegacySecrets || state.sawLegacyAccounts {\n            self.clearLegacyStores(stores: stores, sawAccounts: state.sawLegacyAccounts, log: log)\n        }\n\n        return config.normalized()\n    }\n\n    private static func applyLegacyOrderAndToggles(\n        userDefaults: UserDefaults,\n        config: inout CodexBarConfig,\n        state: inout MigrationState)\n    {\n        if let order = userDefaults.stringArray(forKey: \"providerOrder\"), !order.isEmpty {\n            config = self.applyProviderOrder(order, config: config)\n            state.didUpdate = true\n        }\n        let toggles = userDefaults.dictionary(forKey: \"providerToggles\") as? [String: Bool] ?? [:]\n        if !toggles.isEmpty {\n            config = self.applyProviderToggles(toggles, config: config)\n            state.didUpdate = true\n        }\n    }\n\n    private static func migrateLegacySecrets(\n        userDefaults: UserDefaults,\n        stores: LegacyStores,\n        config: inout CodexBarConfig,\n        state: inout MigrationState)\n    {\n        self.migrateTokenProviders(\n            [\n                (.zai, stores.zaiTokenStore.loadToken),\n                (.synthetic, stores.syntheticTokenStore.loadToken),\n                (.copilot, stores.copilotTokenStore.loadToken),\n                (.kimik2, stores.kimiK2TokenStore.loadToken),\n            ],\n            config: &config,\n            state: &state)\n\n        self.migrateCookieProviders(\n            [\n                (.codex, stores.codexCookieStore.loadCookieHeader),\n                (.claude, stores.claudeCookieStore.loadCookieHeader),\n                (.cursor, stores.cursorCookieStore.loadCookieHeader),\n                (.factory, stores.factoryCookieStore.loadCookieHeader),\n                (.augment, stores.augmentCookieStore.loadCookieHeader),\n                (.amp, stores.ampCookieStore.loadCookieHeader),\n            ],\n            config: &config,\n            state: &state)\n\n        self.migrateMiniMax(userDefaults: userDefaults, stores: stores, config: &config, state: &state)\n        self.migrateKimi(userDefaults: userDefaults, stores: stores, config: &config, state: &state)\n        self.migrateOpenCode(userDefaults: userDefaults, stores: stores, config: &config, state: &state)\n    }\n\n    private static func applyLegacyCookieSources(\n        userDefaults: UserDefaults,\n        config: inout CodexBarConfig,\n        state: inout MigrationState)\n    {\n        let sources: [(UsageProvider, String)] = [\n            (.codex, \"codexCookieSource\"),\n            (.claude, \"claudeCookieSource\"),\n            (.cursor, \"cursorCookieSource\"),\n            (.opencode, \"opencodeCookieSource\"),\n            (.factory, \"factoryCookieSource\"),\n            (.minimax, \"minimaxCookieSource\"),\n            (.kimi, \"kimiCookieSource\"),\n            (.augment, \"augmentCookieSource\"),\n            (.amp, \"ampCookieSource\"),\n        ]\n\n        for (provider, key) in sources {\n            guard let raw = userDefaults.string(forKey: key),\n                  let source = ProviderCookieSource(rawValue: raw)\n            else { continue }\n            self.updateProvider(provider, config: &config, state: &state) { entry in\n                guard entry.cookieSource == nil else { return false }\n                entry.cookieSource = source\n                return true\n            }\n        }\n\n        if userDefaults.object(forKey: \"openAIWebAccessEnabled\") as? Bool == false {\n            self.updateProvider(.codex, config: &config, state: &state) { entry in\n                guard entry.cookieSource == nil else { return false }\n                entry.cookieSource = .off\n                return true\n            }\n        }\n    }\n\n    private static func migrateTokenProviders(\n        _ providers: [(UsageProvider, () throws -> String?)],\n        config: inout CodexBarConfig,\n        state: inout MigrationState)\n    {\n        for (provider, loader) in providers {\n            let token = try? loader()\n            if token != nil { state.sawLegacySecrets = true }\n            self.updateProvider(provider, config: &config, state: &state) { entry in\n                self.setIfEmpty(&entry.apiKey, token)\n            }\n        }\n    }\n\n    private static func migrateCookieProviders(\n        _ providers: [(UsageProvider, () throws -> String?)],\n        config: inout CodexBarConfig,\n        state: inout MigrationState)\n    {\n        for (provider, loader) in providers {\n            let header = try? loader()\n            if header != nil { state.sawLegacySecrets = true }\n            self.updateProvider(provider, config: &config, state: &state) { entry in\n                self.setIfEmpty(&entry.cookieHeader, header)\n            }\n        }\n    }\n\n    private static func migrateMiniMax(\n        userDefaults: UserDefaults,\n        stores: LegacyStores,\n        config: inout CodexBarConfig,\n        state: inout MigrationState)\n    {\n        let token = try? stores.minimaxAPITokenStore.loadToken()\n        let header = try? stores.minimaxCookieStore.loadCookieHeader()\n        if token != nil || header != nil {\n            state.sawLegacySecrets = true\n        }\n        let regionRaw = userDefaults.string(forKey: \"minimaxAPIRegion\")\n        self.updateProvider(.minimax, config: &config, state: &state) { entry in\n            var changed = false\n            changed = self.setIfEmpty(&entry.apiKey, token) || changed\n            if let regionRaw, !regionRaw.isEmpty, entry.region == nil {\n                entry.region = regionRaw\n                changed = true\n            }\n            changed = self.setIfEmpty(&entry.cookieHeader, header) || changed\n            return changed\n        }\n    }\n\n    private static func migrateKimi(\n        userDefaults: UserDefaults,\n        stores: LegacyStores,\n        config: inout CodexBarConfig,\n        state: inout MigrationState)\n    {\n        var token = try? stores.kimiTokenStore.loadToken()\n        if token?.isEmpty ?? true {\n            token = userDefaults.string(forKey: \"kimiManualCookieHeader\")\n        }\n        if token != nil { state.sawLegacySecrets = true }\n        self.updateProvider(.kimi, config: &config, state: &state) { entry in\n            self.setIfEmpty(&entry.cookieHeader, token)\n        }\n    }\n\n    private static func migrateOpenCode(\n        userDefaults: UserDefaults,\n        stores: LegacyStores,\n        config: inout CodexBarConfig,\n        state: inout MigrationState)\n    {\n        let header = try? stores.opencodeCookieStore.loadCookieHeader()\n        if header != nil { state.sawLegacySecrets = true }\n        let workspaceID = userDefaults.string(forKey: \"opencodeWorkspaceID\")\n        self.updateProvider(.opencode, config: &config, state: &state) { entry in\n            var changed = false\n            changed = self.setIfEmpty(&entry.cookieHeader, header) || changed\n            if let workspaceID, !workspaceID.isEmpty, entry.workspaceID == nil {\n                entry.workspaceID = workspaceID\n                changed = true\n            }\n            return changed\n        }\n    }\n\n    private static func migrateLegacyAccounts(\n        stores: LegacyStores,\n        config: inout CodexBarConfig,\n        state: inout MigrationState)\n    {\n        guard let accounts = try? stores.tokenAccountStore.loadAccounts(), !accounts.isEmpty else { return }\n        state.sawLegacyAccounts = true\n        for (provider, data) in accounts where !data.accounts.isEmpty {\n            self.updateProvider(provider, config: &config, state: &state) { entry in\n                guard entry.tokenAccounts == nil else { return false }\n                entry.tokenAccounts = data\n                return true\n            }\n        }\n    }\n\n    private static func updateProvider(\n        _ provider: UsageProvider,\n        config: inout CodexBarConfig,\n        state: inout MigrationState,\n        mutate: (inout ProviderConfig) -> Bool)\n    {\n        guard let index = config.providers.firstIndex(where: { $0.id == provider }) else { return }\n        var entry = config.providers[index]\n        let changed = mutate(&entry)\n        if changed {\n            config.providers[index] = entry\n            state.didUpdate = true\n        }\n    }\n\n    private static func setIfEmpty(_ value: inout String?, _ replacement: String?) -> Bool {\n        let cleaned = replacement?.trimmingCharacters(in: .whitespacesAndNewlines)\n        guard let cleaned, !cleaned.isEmpty else { return false }\n        if value == nil || value?.isEmpty == true {\n            value = cleaned\n            return true\n        }\n        return false\n    }\n\n    private static func clearLegacyStores(\n        stores: LegacyStores,\n        sawAccounts: Bool,\n        log: CodexBarLogger)\n    {\n        do {\n            try stores.zaiTokenStore.storeToken(nil)\n            try stores.syntheticTokenStore.storeToken(nil)\n            try stores.copilotTokenStore.storeToken(nil)\n            try stores.minimaxAPITokenStore.storeToken(nil)\n            try stores.kimiTokenStore.storeToken(nil)\n            try stores.kimiK2TokenStore.storeToken(nil)\n            try stores.codexCookieStore.storeCookieHeader(nil)\n            try stores.claudeCookieStore.storeCookieHeader(nil)\n            try stores.cursorCookieStore.storeCookieHeader(nil)\n            try stores.opencodeCookieStore.storeCookieHeader(nil)\n            try stores.factoryCookieStore.storeCookieHeader(nil)\n            try stores.minimaxCookieStore.storeCookieHeader(nil)\n            try stores.augmentCookieStore.storeCookieHeader(nil)\n            try stores.ampCookieStore.storeCookieHeader(nil)\n        } catch {\n            log.error(\"Failed to clear legacy secrets: \\(error)\")\n        }\n\n        if sawAccounts {\n            let legacyURL = FileTokenAccountStore.defaultURL()\n            if FileManager.default.fileExists(atPath: legacyURL.path) {\n                try? FileManager.default.removeItem(at: legacyURL)\n            }\n        }\n    }\n\n    private static func applyProviderOrder(_ raw: [String], config: CodexBarConfig) -> CodexBarConfig {\n        let configsByID = Dictionary(uniqueKeysWithValues: config.providers.map { ($0.id, $0) })\n        var seen: Set<UsageProvider> = []\n        var ordered: [ProviderConfig] = []\n        ordered.reserveCapacity(config.providers.count)\n\n        for rawValue in raw {\n            guard let provider = UsageProvider(rawValue: rawValue),\n                  let entry = configsByID[provider],\n                  !seen.contains(provider)\n            else { continue }\n            seen.insert(provider)\n            ordered.append(entry)\n        }\n\n        for provider in UsageProvider.allCases where !seen.contains(provider) {\n            ordered.append(configsByID[provider] ?? ProviderConfig(id: provider))\n        }\n\n        var updated = config\n        updated.providers = ordered\n        return updated\n    }\n\n    private static func applyProviderToggles(\n        _ toggles: [String: Bool],\n        config: CodexBarConfig) -> CodexBarConfig\n    {\n        var updated = config\n        for index in updated.providers.indices {\n            let provider = updated.providers[index].id\n            let meta = ProviderDescriptorRegistry.descriptor(for: provider).metadata\n            if let value = toggles[meta.cliName] {\n                updated.providers[index].enabled = value\n            }\n        }\n        return updated\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/CookieHeaderStore.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Security\n\nprotocol CookieHeaderStoring: Sendable {\n    func loadCookieHeader() throws -> String?\n    func storeCookieHeader(_ header: String?) throws\n}\n\nenum CookieHeaderStoreError: LocalizedError {\n    case keychainStatus(OSStatus)\n    case invalidData\n\n    var errorDescription: String? {\n        switch self {\n        case let .keychainStatus(status):\n            \"Keychain error: \\(status)\"\n        case .invalidData:\n            \"Keychain returned invalid data.\"\n        }\n    }\n}\n\nstruct KeychainCookieHeaderStore: CookieHeaderStoring {\n    private static let log = CodexBarLog.logger(LogCategories.cookieHeaderStore)\n\n    private let service = \"com.steipete.CodexBar\"\n    private let account: String\n    private let promptKind: KeychainPromptContext.Kind\n\n    // Cache to reduce keychain access frequency\n    private nonisolated(unsafe) static var cache: [String: CachedValue] = [:]\n    private static let cacheLock = NSLock()\n    private static let cacheTTL: TimeInterval = 1800 // 30 minutes\n\n    private struct CachedValue {\n        let value: String?\n        let timestamp: Date\n\n        var isExpired: Bool {\n            Date().timeIntervalSince(self.timestamp) > KeychainCookieHeaderStore.cacheTTL\n        }\n    }\n\n    init(account: String, promptKind: KeychainPromptContext.Kind) {\n        self.account = account\n        self.promptKind = promptKind\n    }\n\n    func loadCookieHeader() throws -> String? {\n        guard !KeychainAccessGate.isDisabled else {\n            Self.log.debug(\"Keychain access disabled; skipping cookie load\")\n            return nil\n        }\n        // Check cache first\n        Self.cacheLock.lock()\n        if let cached = Self.cache[self.account], !cached.isExpired {\n            Self.cacheLock.unlock()\n            Self.log.debug(\"Using cached cookie header for \\(self.account)\")\n            return cached.value\n        }\n        Self.cacheLock.unlock()\n        var result: CFTypeRef?\n        let query: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: self.service,\n            kSecAttrAccount as String: self.account,\n            kSecMatchLimit as String: kSecMatchLimitOne,\n            kSecReturnData as String: true,\n        ]\n\n        if case .interactionRequired = KeychainAccessPreflight\n            .checkGenericPassword(service: self.service, account: self.account)\n        {\n            KeychainPromptHandler.handler?(KeychainPromptContext(\n                kind: self.promptKind,\n                service: self.service,\n                account: self.account))\n        }\n\n        let status = SecItemCopyMatching(query as CFDictionary, &result)\n        if status == errSecItemNotFound {\n            // Cache the nil result\n            Self.cacheLock.lock()\n            Self.cache[self.account] = CachedValue(value: nil, timestamp: Date())\n            Self.cacheLock.unlock()\n            return nil\n        }\n        guard status == errSecSuccess else {\n            Self.log.error(\"Keychain read failed: \\(status)\")\n            throw CookieHeaderStoreError.keychainStatus(status)\n        }\n\n        guard let data = result as? Data else {\n            throw CookieHeaderStoreError.invalidData\n        }\n        let header = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines)\n        let finalValue = (header?.isEmpty == false) ? header : nil\n\n        // Cache the result\n        Self.cacheLock.lock()\n        Self.cache[self.account] = CachedValue(value: finalValue, timestamp: Date())\n        Self.cacheLock.unlock()\n\n        return finalValue\n    }\n\n    func storeCookieHeader(_ header: String?) throws {\n        guard !KeychainAccessGate.isDisabled else {\n            Self.log.debug(\"Keychain access disabled; skipping cookie store\")\n            return\n        }\n        guard let raw = header?.trimmingCharacters(in: .whitespacesAndNewlines),\n              !raw.isEmpty\n        else {\n            try self.deleteIfPresent()\n            // Invalidate cache\n            Self.cacheLock.lock()\n            Self.cache.removeValue(forKey: self.account)\n            Self.cacheLock.unlock()\n            return\n        }\n        guard CookieHeaderNormalizer.normalize(raw) != nil else {\n            try self.deleteIfPresent()\n            // Invalidate cache\n            Self.cacheLock.lock()\n            Self.cache.removeValue(forKey: self.account)\n            Self.cacheLock.unlock()\n            return\n        }\n\n        let data = raw.data(using: .utf8)!\n        let query: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: self.service,\n            kSecAttrAccount as String: self.account,\n        ]\n        let attributes: [String: Any] = [\n            kSecValueData as String: data,\n            kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,\n        ]\n\n        let updateStatus = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)\n        if updateStatus == errSecSuccess {\n            // Update cache\n            Self.cacheLock.lock()\n            Self.cache[self.account] = CachedValue(value: raw, timestamp: Date())\n            Self.cacheLock.unlock()\n            return\n        }\n        if updateStatus != errSecItemNotFound {\n            Self.log.error(\"Keychain update failed: \\(updateStatus)\")\n            throw CookieHeaderStoreError.keychainStatus(updateStatus)\n        }\n\n        var addQuery = query\n        for (key, value) in attributes {\n            addQuery[key] = value\n        }\n        let addStatus = SecItemAdd(addQuery as CFDictionary, nil)\n        guard addStatus == errSecSuccess else {\n            Self.log.error(\"Keychain add failed: \\(addStatus)\")\n            throw CookieHeaderStoreError.keychainStatus(addStatus)\n        }\n\n        // Update cache\n        Self.cacheLock.lock()\n        Self.cache[self.account] = CachedValue(value: raw, timestamp: Date())\n        Self.cacheLock.unlock()\n    }\n\n    private func deleteIfPresent() throws {\n        guard !KeychainAccessGate.isDisabled else { return }\n        let query: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: self.service,\n            kSecAttrAccount as String: self.account,\n        ]\n        let status = SecItemDelete(query as CFDictionary)\n        if status == errSecSuccess || status == errSecItemNotFound {\n            // Invalidate cache\n            Self.cacheLock.lock()\n            Self.cache.removeValue(forKey: self.account)\n            Self.cacheLock.unlock()\n            return\n        }\n        Self.log.error(\"Keychain delete failed: \\(status)\")\n        throw CookieHeaderStoreError.keychainStatus(status)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/CopilotTokenStore.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Security\n\nprotocol CopilotTokenStoring: Sendable {\n    func loadToken() throws -> String?\n    func storeToken(_ token: String?) throws\n}\n\nenum CopilotTokenStoreError: LocalizedError {\n    case keychainStatus(OSStatus)\n    case invalidData\n\n    var errorDescription: String? {\n        switch self {\n        case let .keychainStatus(status):\n            \"Keychain error: \\(status)\"\n        case .invalidData:\n            \"Keychain returned invalid data.\"\n        }\n    }\n}\n\nstruct KeychainCopilotTokenStore: CopilotTokenStoring {\n    private static let log = CodexBarLog.logger(LogCategories.copilotTokenStore)\n\n    private let service = \"com.steipete.CodexBar\"\n    private let account = \"copilot-api-token\"\n\n    func loadToken() throws -> String? {\n        guard !KeychainAccessGate.isDisabled else {\n            Self.log.debug(\"Keychain access disabled; skipping token load\")\n            return nil\n        }\n        var result: CFTypeRef?\n        let query: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: self.service,\n            kSecAttrAccount as String: self.account,\n            kSecMatchLimit as String: kSecMatchLimitOne,\n            kSecReturnData as String: true,\n        ]\n\n        if case .interactionRequired = KeychainAccessPreflight\n            .checkGenericPassword(service: self.service, account: self.account)\n        {\n            KeychainPromptHandler.handler?(KeychainPromptContext(\n                kind: .copilotToken,\n                service: self.service,\n                account: self.account))\n        }\n\n        let status = SecItemCopyMatching(query as CFDictionary, &result)\n        if status == errSecItemNotFound {\n            return nil\n        }\n        guard status == errSecSuccess else {\n            Self.log.error(\"Keychain read failed: \\(status)\")\n            throw CopilotTokenStoreError.keychainStatus(status)\n        }\n\n        guard let data = result as? Data else {\n            throw CopilotTokenStoreError.invalidData\n        }\n        let token = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines)\n        if let token, !token.isEmpty {\n            return token\n        }\n        return nil\n    }\n\n    func storeToken(_ token: String?) throws {\n        guard !KeychainAccessGate.isDisabled else {\n            Self.log.debug(\"Keychain access disabled; skipping token store\")\n            return\n        }\n        let cleaned = token?.trimmingCharacters(in: .whitespacesAndNewlines)\n        if cleaned == nil || cleaned?.isEmpty == true {\n            try self.deleteTokenIfPresent()\n            return\n        }\n\n        let data = cleaned!.data(using: .utf8)!\n        let query: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: self.service,\n            kSecAttrAccount as String: self.account,\n        ]\n        let attributes: [String: Any] = [\n            kSecValueData as String: data,\n            kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,\n        ]\n\n        let updateStatus = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)\n        if updateStatus == errSecSuccess {\n            return\n        }\n        if updateStatus != errSecItemNotFound {\n            Self.log.error(\"Keychain update failed: \\(updateStatus)\")\n            throw CopilotTokenStoreError.keychainStatus(updateStatus)\n        }\n\n        var addQuery = query\n        for (key, value) in attributes {\n            addQuery[key] = value\n        }\n        let addStatus = SecItemAdd(addQuery as CFDictionary, nil)\n        guard addStatus == errSecSuccess else {\n            Self.log.error(\"Keychain add failed: \\(addStatus)\")\n            throw CopilotTokenStoreError.keychainStatus(addStatus)\n        }\n    }\n\n    private func deleteTokenIfPresent() throws {\n        guard !KeychainAccessGate.isDisabled else { return }\n        let query: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: self.service,\n            kSecAttrAccount as String: self.account,\n        ]\n        let status = SecItemDelete(query as CFDictionary)\n        if status == errSecSuccess || status == errSecItemNotFound {\n            return\n        }\n        Self.log.error(\"Keychain delete failed: \\(status)\")\n        throw CopilotTokenStoreError.keychainStatus(status)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/CostHistoryChartMenuView.swift",
    "content": "import Charts\nimport CodexBarCore\nimport SwiftUI\n\n@MainActor\nstruct CostHistoryChartMenuView: View {\n    typealias DailyEntry = CostUsageDailyReport.Entry\n\n    private struct Point: Identifiable {\n        let id: String\n        let date: Date\n        let costUSD: Double\n        let totalTokens: Int?\n\n        init(date: Date, costUSD: Double, totalTokens: Int?) {\n            self.date = date\n            self.costUSD = costUSD\n            self.totalTokens = totalTokens\n            self.id = \"\\(Int(date.timeIntervalSince1970))-\\(costUSD)\"\n        }\n    }\n\n    private let provider: UsageProvider\n    private let daily: [DailyEntry]\n    private let totalCostUSD: Double?\n    private let width: CGFloat\n    @State private var selectedDateKey: String?\n\n    init(provider: UsageProvider, daily: [DailyEntry], totalCostUSD: Double?, width: CGFloat) {\n        self.provider = provider\n        self.daily = daily\n        self.totalCostUSD = totalCostUSD\n        self.width = width\n    }\n\n    var body: some View {\n        let model = Self.makeModel(provider: self.provider, daily: self.daily)\n        VStack(alignment: .leading, spacing: 10) {\n            if model.points.isEmpty {\n                Text(\"No cost history data.\")\n                    .font(.footnote)\n                    .foregroundStyle(.secondary)\n            } else {\n                Chart {\n                    ForEach(model.points) { point in\n                        BarMark(\n                            x: .value(\"Day\", point.date, unit: .day),\n                            y: .value(\"Cost\", point.costUSD))\n                            .foregroundStyle(model.barColor)\n                    }\n                    if let peak = Self.peakPoint(model: model) {\n                        let capStart = max(peak.costUSD - Self.capHeight(maxValue: model.maxCostUSD), 0)\n                        BarMark(\n                            x: .value(\"Day\", peak.date, unit: .day),\n                            yStart: .value(\"Cap start\", capStart),\n                            yEnd: .value(\"Cap end\", peak.costUSD))\n                            .foregroundStyle(Color(nsColor: .systemYellow))\n                    }\n                }\n                .chartYAxis(.hidden)\n                .chartXAxis {\n                    AxisMarks(values: model.axisDates) { _ in\n                        AxisGridLine().foregroundStyle(Color.clear)\n                        AxisTick().foregroundStyle(Color.clear)\n                        AxisValueLabel(format: .dateTime.month(.abbreviated).day())\n                            .font(.caption2)\n                            .foregroundStyle(Color(nsColor: .tertiaryLabelColor))\n                    }\n                }\n                .chartLegend(.hidden)\n                .frame(height: 130)\n                .chartOverlay { proxy in\n                    GeometryReader { geo in\n                        ZStack(alignment: .topLeading) {\n                            if let rect = self.selectionBandRect(model: model, proxy: proxy, geo: geo) {\n                                Rectangle()\n                                    .fill(Self.selectionBandColor)\n                                    .frame(width: rect.width, height: rect.height)\n                                    .position(x: rect.midX, y: rect.midY)\n                                    .allowsHitTesting(false)\n                            }\n                            MouseLocationReader { location in\n                                self.updateSelection(location: location, model: model, proxy: proxy, geo: geo)\n                            }\n                            .frame(maxWidth: .infinity, maxHeight: .infinity)\n                            .contentShape(Rectangle())\n                        }\n                    }\n                }\n\n                let detail = self.detailLines(model: model)\n                VStack(alignment: .leading, spacing: 0) {\n                    Text(detail.primary)\n                        .font(.caption)\n                        .foregroundStyle(.secondary)\n                        .lineLimit(1)\n                        .truncationMode(.tail)\n                        .frame(height: 16, alignment: .leading)\n                    Text(detail.secondary ?? \" \")\n                        .font(.caption)\n                        .foregroundStyle(.secondary)\n                        .lineLimit(1)\n                        .truncationMode(.tail)\n                        .frame(height: 16, alignment: .leading)\n                        .opacity(detail.secondary == nil ? 0 : 1)\n                }\n            }\n\n            if let total = self.totalCostUSD {\n                Text(\"Total (30d): \\(UsageFormatter.usdString(total))\")\n                    .font(.caption)\n                    .foregroundStyle(.secondary)\n            }\n        }\n        .padding(.horizontal, 16)\n        .padding(.vertical, 10)\n        .frame(minWidth: self.width, maxWidth: .infinity, alignment: .leading)\n    }\n\n    private struct Model {\n        let points: [Point]\n        let pointsByDateKey: [String: Point]\n        let entriesByDateKey: [String: DailyEntry]\n        let dateKeys: [(key: String, date: Date)]\n        let axisDates: [Date]\n        let barColor: Color\n        let peakKey: String?\n        let maxCostUSD: Double\n    }\n\n    private static let selectionBandColor = Color(nsColor: .labelColor).opacity(0.1)\n\n    private static func capHeight(maxValue: Double) -> Double {\n        maxValue * 0.05\n    }\n\n    private static func makeModel(provider: UsageProvider, daily: [DailyEntry]) -> Model {\n        let sorted = daily.sorted { lhs, rhs in lhs.date < rhs.date }\n        var points: [Point] = []\n        points.reserveCapacity(sorted.count)\n\n        var pointsByKey: [String: Point] = [:]\n        pointsByKey.reserveCapacity(sorted.count)\n\n        var entriesByKey: [String: DailyEntry] = [:]\n        entriesByKey.reserveCapacity(sorted.count)\n\n        var dateKeys: [(key: String, date: Date)] = []\n        dateKeys.reserveCapacity(sorted.count)\n\n        var peak: (key: String, costUSD: Double)?\n        var maxCostUSD: Double = 0\n        for entry in sorted {\n            guard let costUSD = entry.costUSD, costUSD >= 0 else { continue }\n            guard let date = self.dateFromDayKey(entry.date) else { continue }\n            let point = Point(date: date, costUSD: costUSD, totalTokens: entry.totalTokens)\n            points.append(point)\n            pointsByKey[entry.date] = point\n            entriesByKey[entry.date] = entry\n            dateKeys.append((entry.date, date))\n            if let cur = peak {\n                if costUSD > cur.costUSD { peak = (entry.date, costUSD) }\n            } else {\n                peak = (entry.date, costUSD)\n            }\n            maxCostUSD = max(maxCostUSD, costUSD)\n        }\n\n        let axisDates: [Date] = {\n            guard let first = dateKeys.first?.date, let last = dateKeys.last?.date else { return [] }\n            if Calendar.current.isDate(first, inSameDayAs: last) { return [first] }\n            return [first, last]\n        }()\n\n        let barColor = Self.barColor(for: provider)\n        return Model(\n            points: points,\n            pointsByDateKey: pointsByKey,\n            entriesByDateKey: entriesByKey,\n            dateKeys: dateKeys,\n            axisDates: axisDates,\n            barColor: barColor,\n            peakKey: maxCostUSD > 0 ? peak?.key : nil,\n            maxCostUSD: maxCostUSD)\n    }\n\n    private static func barColor(for provider: UsageProvider) -> Color {\n        let color = ProviderDescriptorRegistry.descriptor(for: provider).branding.color\n        return Color(red: color.red, green: color.green, blue: color.blue)\n    }\n\n    private static func dateFromDayKey(_ key: String) -> Date? {\n        let parts = key.split(separator: \"-\")\n        guard parts.count == 3,\n              let year = Int(parts[0]),\n              let month = Int(parts[1]),\n              let day = Int(parts[2]) else { return nil }\n\n        var comps = DateComponents()\n        comps.calendar = Calendar.current\n        comps.timeZone = TimeZone.current\n        comps.year = year\n        comps.month = month\n        comps.day = day\n        comps.hour = 12\n        return comps.date\n    }\n\n    private static func peakPoint(model: Model) -> Point? {\n        guard let key = model.peakKey else { return nil }\n        return model.pointsByDateKey[key]\n    }\n\n    private func selectionBandRect(model: Model, proxy: ChartProxy, geo: GeometryProxy) -> CGRect? {\n        guard let key = self.selectedDateKey else { return nil }\n        guard let plotAnchor = proxy.plotFrame else { return nil }\n        let plotFrame = geo[plotAnchor]\n        guard let index = model.dateKeys.firstIndex(where: { $0.key == key }) else { return nil }\n        let date = model.dateKeys[index].date\n        guard let x = proxy.position(forX: date) else { return nil }\n\n        func xForIndex(_ idx: Int) -> CGFloat? {\n            guard idx >= 0, idx < model.dateKeys.count else { return nil }\n            return proxy.position(forX: model.dateKeys[idx].date)\n        }\n\n        let xPrev = xForIndex(index - 1)\n        let xNext = xForIndex(index + 1)\n\n        let leftInPlot: CGFloat = if let xPrev {\n            (xPrev + x) / 2\n        } else if let xNext {\n            x - (xNext - x) / 2\n        } else {\n            x - 8\n        }\n\n        let rightInPlot: CGFloat = if let xNext {\n            (xNext + x) / 2\n        } else if let xPrev {\n            x + (x - xPrev) / 2\n        } else {\n            x + 8\n        }\n\n        let left = plotFrame.origin.x + min(leftInPlot, rightInPlot)\n        let right = plotFrame.origin.x + max(leftInPlot, rightInPlot)\n        return CGRect(x: left, y: plotFrame.origin.y, width: right - left, height: plotFrame.height)\n    }\n\n    private func updateSelection(\n        location: CGPoint?,\n        model: Model,\n        proxy: ChartProxy,\n        geo: GeometryProxy)\n    {\n        guard let location else {\n            if self.selectedDateKey != nil { self.selectedDateKey = nil }\n            return\n        }\n\n        guard let plotAnchor = proxy.plotFrame else { return }\n        let plotFrame = geo[plotAnchor]\n        guard plotFrame.contains(location) else { return }\n\n        let xInPlot = location.x - plotFrame.origin.x\n        guard let date: Date = proxy.value(atX: xInPlot) else { return }\n        guard let nearest = self.nearestDateKey(to: date, model: model) else { return }\n\n        if self.selectedDateKey != nearest {\n            self.selectedDateKey = nearest\n        }\n    }\n\n    private func nearestDateKey(to date: Date, model: Model) -> String? {\n        guard !model.dateKeys.isEmpty else { return nil }\n        var best: (key: String, distance: TimeInterval)?\n        for entry in model.dateKeys {\n            let dist = abs(entry.date.timeIntervalSince(date))\n            if let cur = best {\n                if dist < cur.distance { best = (entry.key, dist) }\n            } else {\n                best = (entry.key, dist)\n            }\n        }\n        return best?.key\n    }\n\n    private func detailLines(model: Model) -> (primary: String, secondary: String?) {\n        guard let key = self.selectedDateKey,\n              let point = model.pointsByDateKey[key],\n              let date = Self.dateFromDayKey(key)\n        else {\n            return (\"Hover a bar for details\", nil)\n        }\n\n        let dayLabel = date.formatted(.dateTime.month(.abbreviated).day())\n        let cost = UsageFormatter.usdString(point.costUSD)\n        if let tokens = point.totalTokens {\n            let primary = \"\\(dayLabel): \\(cost) · \\(UsageFormatter.tokenCountString(tokens)) tokens\"\n            let secondary = self.topModelsText(key: key, model: model)\n            return (primary, secondary)\n        }\n        let primary = \"\\(dayLabel): \\(cost)\"\n        let secondary = self.topModelsText(key: key, model: model)\n        return (primary, secondary)\n    }\n\n    private func topModelsText(key: String, model: Model) -> String? {\n        guard let entry = model.entriesByDateKey[key] else { return nil }\n        guard let breakdown = entry.modelBreakdowns, !breakdown.isEmpty else { return nil }\n        let parts = breakdown\n            .compactMap { item -> String? in\n                let name = UsageFormatter.modelDisplayName(item.modelName)\n                guard let detail = UsageFormatter.modelCostDetail(\n                    item.modelName,\n                    costUSD: item.costUSD,\n                    totalTokens: item.totalTokens)\n                else { return nil }\n                return \"\\(name) \\(detail)\"\n            }\n            .prefix(3)\n        guard !parts.isEmpty else { return nil }\n        return \"Top: \\(parts.joined(separator: \" · \"))\"\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/CreditsHistoryChartMenuView.swift",
    "content": "import Charts\nimport CodexBarCore\nimport SwiftUI\n\n@MainActor\nstruct CreditsHistoryChartMenuView: View {\n    private struct Point: Identifiable {\n        let id: String\n        let date: Date\n        let creditsUsed: Double\n\n        init(date: Date, creditsUsed: Double) {\n            self.date = date\n            self.creditsUsed = creditsUsed\n            self.id = \"\\(Int(date.timeIntervalSince1970))-\\(creditsUsed)\"\n        }\n    }\n\n    private let breakdown: [OpenAIDashboardDailyBreakdown]\n    private let width: CGFloat\n    @State private var selectedDayKey: String?\n\n    init(breakdown: [OpenAIDashboardDailyBreakdown], width: CGFloat) {\n        self.breakdown = breakdown\n        self.width = width\n    }\n\n    var body: some View {\n        let model = Self.makeModel(from: self.breakdown)\n        VStack(alignment: .leading, spacing: 10) {\n            if model.points.isEmpty {\n                Text(\"No credits history data.\")\n                    .font(.footnote)\n                    .foregroundStyle(.secondary)\n            } else {\n                Chart {\n                    ForEach(model.points) { point in\n                        BarMark(\n                            x: .value(\"Day\", point.date, unit: .day),\n                            y: .value(\"Credits used\", point.creditsUsed))\n                            .foregroundStyle(Self.barColor)\n                    }\n                    if let peak = Self.peakPoint(model: model) {\n                        let capStart = max(peak.creditsUsed - Self.capHeight(maxValue: model.maxCreditsUsed), 0)\n                        BarMark(\n                            x: .value(\"Day\", peak.date, unit: .day),\n                            yStart: .value(\"Cap start\", capStart),\n                            yEnd: .value(\"Cap end\", peak.creditsUsed))\n                            .foregroundStyle(Color(nsColor: .systemYellow))\n                    }\n                }\n                .chartYAxis(.hidden)\n                .chartXAxis {\n                    AxisMarks(values: model.axisDates) { _ in\n                        AxisGridLine().foregroundStyle(Color.clear)\n                        AxisTick().foregroundStyle(Color.clear)\n                        AxisValueLabel(format: .dateTime.month(.abbreviated).day())\n                            .font(.caption2)\n                            .foregroundStyle(Color(nsColor: .tertiaryLabelColor))\n                    }\n                }\n                .chartLegend(.hidden)\n                .frame(height: 130)\n                .chartOverlay { proxy in\n                    GeometryReader { geo in\n                        ZStack(alignment: .topLeading) {\n                            if let rect = self.selectionBandRect(model: model, proxy: proxy, geo: geo) {\n                                Rectangle()\n                                    .fill(Self.selectionBandColor)\n                                    .frame(width: rect.width, height: rect.height)\n                                    .position(x: rect.midX, y: rect.midY)\n                                    .allowsHitTesting(false)\n                            }\n                            MouseLocationReader { location in\n                                self.updateSelection(location: location, model: model, proxy: proxy, geo: geo)\n                            }\n                            .frame(maxWidth: .infinity, maxHeight: .infinity)\n                            .contentShape(Rectangle())\n                        }\n                    }\n                }\n\n                let detail = self.detailLines(model: model)\n                VStack(alignment: .leading, spacing: 0) {\n                    Text(detail.primary)\n                        .font(.caption)\n                        .foregroundStyle(.secondary)\n                        .lineLimit(1)\n                        .truncationMode(.tail)\n                        .frame(height: 16, alignment: .leading)\n                    Text(detail.secondary ?? \" \")\n                        .font(.caption)\n                        .foregroundStyle(.secondary)\n                        .lineLimit(1)\n                        .truncationMode(.tail)\n                        .frame(height: 16, alignment: .leading)\n                        .opacity(detail.secondary == nil ? 0 : 1)\n                }\n\n                if let total = model.totalCreditsUsed {\n                    Text(\"Total (30d): \\(total.formatted(.number.precision(.fractionLength(0...2)))) credits\")\n                        .font(.caption)\n                        .foregroundStyle(.secondary)\n                }\n            }\n        }\n        .padding(.horizontal, 16)\n        .padding(.vertical, 10)\n        .frame(minWidth: self.width, maxWidth: .infinity, alignment: .leading)\n    }\n\n    private struct Model {\n        let points: [Point]\n        let breakdownByDayKey: [String: OpenAIDashboardDailyBreakdown]\n        let pointsByDayKey: [String: Point]\n        let dayDates: [(dayKey: String, date: Date)]\n        let selectableDayDates: [(dayKey: String, date: Date)]\n        let axisDates: [Date]\n        let peakKey: String?\n        let totalCreditsUsed: Double?\n        let maxCreditsUsed: Double\n    }\n\n    private static let barColor = Color(red: 73 / 255, green: 163 / 255, blue: 176 / 255)\n    private static let selectionBandColor = Color(nsColor: .labelColor).opacity(0.1)\n    private static func capHeight(maxValue: Double) -> Double {\n        maxValue * 0.05\n    }\n\n    private static func makeModel(from breakdown: [OpenAIDashboardDailyBreakdown]) -> Model {\n        let sorted = breakdown.sorted { lhs, rhs in lhs.day < rhs.day }\n\n        var points: [Point] = []\n        points.reserveCapacity(sorted.count)\n\n        var breakdownByDayKey: [String: OpenAIDashboardDailyBreakdown] = [:]\n        breakdownByDayKey.reserveCapacity(sorted.count)\n\n        var pointsByDayKey: [String: Point] = [:]\n        pointsByDayKey.reserveCapacity(sorted.count)\n\n        var dayDates: [(dayKey: String, date: Date)] = []\n        dayDates.reserveCapacity(sorted.count)\n\n        var selectableDayDates: [(dayKey: String, date: Date)] = []\n        selectableDayDates.reserveCapacity(sorted.count)\n\n        var totalCreditsUsed: Double = 0\n        var peak: (key: String, creditsUsed: Double)?\n        var maxCreditsUsed: Double = 0\n\n        for day in sorted {\n            guard let date = self.dateFromDayKey(day.day) else { continue }\n            breakdownByDayKey[day.day] = day\n            dayDates.append((dayKey: day.day, date: date))\n            totalCreditsUsed += day.totalCreditsUsed\n            if day.totalCreditsUsed > 0 {\n                let point = Point(date: date, creditsUsed: day.totalCreditsUsed)\n                points.append(point)\n                pointsByDayKey[day.day] = point\n                selectableDayDates.append((dayKey: day.day, date: date))\n                if let cur = peak {\n                    if day.totalCreditsUsed > cur.creditsUsed { peak = (day.day, day.totalCreditsUsed) }\n                } else {\n                    peak = (day.day, day.totalCreditsUsed)\n                }\n                maxCreditsUsed = max(maxCreditsUsed, day.totalCreditsUsed)\n            }\n        }\n\n        let axisDates: [Date] = {\n            guard let first = dayDates.first?.date, let last = dayDates.last?.date else { return [] }\n            if Calendar.current.isDate(first, inSameDayAs: last) { return [first] }\n            return [first, last]\n        }()\n\n        return Model(\n            points: points,\n            breakdownByDayKey: breakdownByDayKey,\n            pointsByDayKey: pointsByDayKey,\n            dayDates: dayDates,\n            selectableDayDates: selectableDayDates,\n            axisDates: axisDates,\n            peakKey: peak?.key,\n            totalCreditsUsed: totalCreditsUsed > 0 ? totalCreditsUsed : nil,\n            maxCreditsUsed: maxCreditsUsed)\n    }\n\n    private static func dateFromDayKey(_ key: String) -> Date? {\n        let parts = key.split(separator: \"-\")\n        guard parts.count == 3,\n              let year = Int(parts[0]),\n              let month = Int(parts[1]),\n              let day = Int(parts[2])\n        else {\n            return nil\n        }\n\n        var comps = DateComponents()\n        comps.calendar = Calendar.current\n        comps.timeZone = TimeZone.current\n        comps.year = year\n        comps.month = month\n        comps.day = day\n        comps.hour = 12\n        return comps.date\n    }\n\n    private static func peakPoint(model: Model) -> Point? {\n        guard let key = model.peakKey else { return nil }\n        return model.pointsByDayKey[key]\n    }\n\n    private func selectionBandRect(model: Model, proxy: ChartProxy, geo: GeometryProxy) -> CGRect? {\n        guard let key = self.selectedDayKey else { return nil }\n        guard let plotAnchor = proxy.plotFrame else { return nil }\n        let plotFrame = geo[plotAnchor]\n        guard let index = model.dayDates.firstIndex(where: { $0.dayKey == key }) else { return nil }\n        let date = model.dayDates[index].date\n        guard let x = proxy.position(forX: date) else { return nil }\n\n        func xForIndex(_ idx: Int) -> CGFloat? {\n            guard idx >= 0, idx < model.dayDates.count else { return nil }\n            return proxy.position(forX: model.dayDates[idx].date)\n        }\n\n        let xPrev = xForIndex(index - 1)\n        let xNext = xForIndex(index + 1)\n\n        if model.dayDates.count <= 1 {\n            return CGRect(\n                x: plotFrame.origin.x,\n                y: plotFrame.origin.y,\n                width: plotFrame.width,\n                height: plotFrame.height)\n        }\n\n        let leftInPlot: CGFloat = if let xPrev {\n            (xPrev + x) / 2\n        } else if let xNext {\n            x - (xNext - x) / 2\n        } else {\n            x - 8\n        }\n\n        let rightInPlot: CGFloat = if let xNext {\n            (xNext + x) / 2\n        } else if let xPrev {\n            x + (x - xPrev) / 2\n        } else {\n            x + 8\n        }\n\n        let left = plotFrame.origin.x + min(leftInPlot, rightInPlot)\n        let right = plotFrame.origin.x + max(leftInPlot, rightInPlot)\n        return CGRect(x: left, y: plotFrame.origin.y, width: right - left, height: plotFrame.height)\n    }\n\n    private func updateSelection(\n        location: CGPoint?,\n        model: Model,\n        proxy: ChartProxy,\n        geo: GeometryProxy)\n    {\n        guard let location else {\n            if self.selectedDayKey != nil { self.selectedDayKey = nil }\n            return\n        }\n\n        guard let plotAnchor = proxy.plotFrame else { return }\n        let plotFrame = geo[plotAnchor]\n        guard plotFrame.contains(location) else { return }\n\n        let xInPlot = location.x - plotFrame.origin.x\n        guard let date: Date = proxy.value(atX: xInPlot) else { return }\n        guard let nearest = self.nearestDayKey(to: date, model: model) else { return }\n\n        if self.selectedDayKey != nearest {\n            self.selectedDayKey = nearest\n        }\n    }\n\n    private func nearestDayKey(to date: Date, model: Model) -> String? {\n        guard !model.selectableDayDates.isEmpty else { return nil }\n        var best: (key: String, distance: TimeInterval)?\n        for entry in model.selectableDayDates {\n            let dist = abs(entry.date.timeIntervalSince(date))\n            if let cur = best {\n                if dist < cur.distance { best = (entry.dayKey, dist) }\n            } else {\n                best = (entry.dayKey, dist)\n            }\n        }\n        return best?.key\n    }\n\n    private func detailLines(model: Model) -> (primary: String, secondary: String?) {\n        guard let key = self.selectedDayKey,\n              let day = model.breakdownByDayKey[key],\n              let date = Self.dateFromDayKey(key)\n        else {\n            return (\"Hover a bar for details\", nil)\n        }\n\n        let dayLabel = date.formatted(.dateTime.month(.abbreviated).day())\n        let total = day.totalCreditsUsed.formatted(.number.precision(.fractionLength(0...2)))\n        if day.services.isEmpty {\n            return (\"\\(dayLabel): \\(total) credits\", nil)\n        }\n        if day.services.count <= 1, let first = day.services.first {\n            let used = first.creditsUsed.formatted(.number.precision(.fractionLength(0...2)))\n            return (\"\\(dayLabel): \\(used) credits\", first.service)\n        }\n\n        let services = day.services\n            .sorted { lhs, rhs in\n                if lhs.creditsUsed == rhs.creditsUsed { return lhs.service < rhs.service }\n                return lhs.creditsUsed > rhs.creditsUsed\n            }\n            .prefix(3)\n            .map { \"\\($0.service) \\($0.creditsUsed.formatted(.number.precision(.fractionLength(0...2))))\" }\n            .joined(separator: \" · \")\n\n        return (\"\\(dayLabel): \\(total) credits\", services)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/CursorLoginRunner.swift",
    "content": "import AppKit\nimport CodexBarCore\nimport Foundation\nimport WebKit\n\n/// Handles Cursor login flow using a WebKit-based browser window.\n/// Captures session cookies after successful authentication.\n@MainActor\nfinal class CursorLoginRunner: NSObject {\n    enum Phase {\n        case loading\n        case waitingLogin\n        case success\n        case failed(String)\n    }\n\n    struct Result {\n        enum Outcome {\n            case success\n            case cancelled\n            case failed(String)\n        }\n\n        let outcome: Outcome\n        let email: String?\n    }\n\n    private let browserDetection: BrowserDetection\n    private var webView: WKWebView?\n    private var window: NSWindow?\n    private var continuation: CheckedContinuation<Result, Never>?\n    private var phaseCallback: ((Phase) -> Void)?\n    private var hasCompletedLogin = false\n    private let logger = CodexBarLog.logger(LogCategories.cursorLogin)\n\n    private static let dashboardURL = URL(string: \"https://cursor.com/dashboard\")!\n    private static let loginURLPattern = \"authenticator.cursor.sh\"\n\n    init(browserDetection: BrowserDetection) {\n        self.browserDetection = browserDetection\n        super.init()\n    }\n\n    /// Runs the Cursor login flow in a browser window.\n    /// Returns the result after the user completes login or cancels.\n    func run(onPhaseChange: @escaping @Sendable (Phase) -> Void) async -> Result {\n        // Keep this instance alive during the flow.\n        WebKitTeardown.retain(self)\n        self.phaseCallback = onPhaseChange\n        onPhaseChange(.loading)\n        self.logger.info(\"Cursor login started\")\n\n        return await withCheckedContinuation { continuation in\n            self.continuation = continuation\n            self.setupWindow()\n        }\n    }\n\n    private func setupWindow() {\n        // Use a non-persistent store for the login flow; cookies are persisted explicitly.\n        let config = WKWebViewConfiguration()\n        config.websiteDataStore = .nonPersistent()\n\n        let webView = WKWebView(frame: NSRect(x: 0, y: 0, width: 480, height: 640), configuration: config)\n        webView.navigationDelegate = self\n        self.webView = webView\n\n        // Create window\n        let window = NSWindow(\n            contentRect: NSRect(x: 0, y: 0, width: 480, height: 640),\n            styleMask: [.titled, .closable, .resizable],\n            backing: .buffered,\n            defer: false)\n        window.isReleasedWhenClosed = false\n        window.title = \"Cursor Login\"\n        window.contentView = webView\n        window.center()\n        window.delegate = self\n        window.makeKeyAndOrderFront(nil)\n        self.window = window\n        self.logger.info(\"Cursor login window opened\")\n\n        // Navigate to dashboard (will redirect to login if not authenticated)\n        let request = URLRequest(url: Self.dashboardURL)\n        webView.load(request)\n    }\n\n    private func complete(with result: Result) {\n        guard let continuation = self.continuation else { return }\n        self.continuation = nil\n        self.logger.info(\"Cursor login completed\", metadata: [\"outcome\": \"\\(result.outcome)\"])\n        self.scheduleCleanup()\n        continuation.resume(returning: result)\n    }\n\n    private func scheduleCleanup() {\n        self.logger.info(\"Cursor login window closing\")\n        WebKitTeardown.scheduleCleanup(owner: self, window: self.window, webView: self.webView)\n    }\n\n    private func captureSessionCookies() async {\n        guard let webView = self.webView else { return }\n\n        let dataStore = webView.configuration.websiteDataStore\n        let cookies = await dataStore.httpCookieStore.allCookies()\n\n        // Filter for cursor.com cookies\n        let cursorCookies = cookies.filter { cookie in\n            cookie.domain.contains(\"cursor.com\") || cookie.domain.contains(\"cursor.sh\")\n        }\n\n        guard !cursorCookies.isEmpty else {\n            self.phaseCallback?(.failed(\"No session cookies found\"))\n            self.logger.warning(\"Cursor login failed: no session cookies found\")\n            self.complete(with: Result(outcome: .failed(\"No session cookies found\"), email: nil))\n            return\n        }\n\n        // Save cookies to the session store\n        await CursorSessionStore.shared.setCookies(cursorCookies)\n        self.logger.info(\"Cursor session cookies captured\", metadata: [\"count\": \"\\(cursorCookies.count)\"])\n\n        // Try to get user email\n        let email = await self.fetchUserEmail()\n\n        self.hasCompletedLogin = true\n        self.phaseCallback?(.success)\n        self.complete(with: Result(outcome: .success, email: email))\n    }\n\n    private func fetchUserEmail() async -> String? {\n        do {\n            let probe = CursorStatusProbe(browserDetection: self.browserDetection)\n            let snapshot = try await probe.fetch()\n            return snapshot.accountEmail\n        } catch {\n            return nil\n        }\n    }\n}\n\n// MARK: - WKNavigationDelegate\n\nextension CursorLoginRunner: WKNavigationDelegate {\n    nonisolated func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {\n        Task { @MainActor in\n            guard let url = webView.url else { return }\n\n            let urlString = url.absoluteString\n\n            // Check if on login page\n            if urlString.contains(Self.loginURLPattern) {\n                self.phaseCallback?(.waitingLogin)\n                return\n            }\n\n            // Check if on dashboard (login successful)\n            if urlString.contains(\"cursor.com/dashboard\"), !self.hasCompletedLogin {\n                await self.captureSessionCookies()\n            }\n        }\n    }\n\n    nonisolated func webView(\n        _ webView: WKWebView,\n        didReceiveServerRedirectForProvisionalNavigation navigation: WKNavigation!)\n    {\n        Task { @MainActor in\n            guard let url = webView.url else { return }\n            let urlString = url.absoluteString\n\n            // Detect redirect to dashboard after login\n            if urlString.contains(\"cursor.com/dashboard\"), !self.hasCompletedLogin {\n                // Wait a moment for cookies to be set, then capture\n                try? await Task.sleep(nanoseconds: 500_000_000)\n                await self.captureSessionCookies()\n            }\n        }\n    }\n\n    nonisolated func webView(\n        _ webView: WKWebView,\n        didFail navigation: WKNavigation!,\n        withError error: Error)\n    {\n        Task { @MainActor in\n            self.phaseCallback?(.failed(error.localizedDescription))\n            self.logger.error(\"Cursor login navigation failed\", metadata: [\"error\": error.localizedDescription])\n            self.complete(with: Result(outcome: .failed(error.localizedDescription), email: nil))\n        }\n    }\n\n    nonisolated func webView(\n        _ webView: WKWebView,\n        didFailProvisionalNavigation navigation: WKNavigation!,\n        withError error: Error)\n    {\n        Task { @MainActor in\n            // Ignore cancelled navigations (common during redirects)\n            let nsError = error as NSError\n            if nsError.domain == NSURLErrorDomain, nsError.code == NSURLErrorCancelled {\n                return\n            }\n            self.phaseCallback?(.failed(error.localizedDescription))\n            self.logger.error(\"Cursor login navigation failed\", metadata: [\"error\": error.localizedDescription])\n            self.complete(with: Result(outcome: .failed(error.localizedDescription), email: nil))\n        }\n    }\n}\n\n// MARK: - NSWindowDelegate\n\nextension CursorLoginRunner: NSWindowDelegate {\n    nonisolated func windowWillClose(_ notification: Notification) {\n        Task { @MainActor in\n            if !self.hasCompletedLogin {\n                self.logger.info(\"Cursor login cancelled\")\n                self.complete(with: Result(outcome: .cancelled, email: nil))\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Date+RelativeDescription.swift",
    "content": "import Foundation\n\nenum RelativeTimeFormatters {\n    @MainActor\n    static let full: RelativeDateTimeFormatter = {\n        let formatter = RelativeDateTimeFormatter()\n        formatter.unitsStyle = .full\n        return formatter\n    }()\n}\n\nextension Date {\n    @MainActor\n    func relativeDescription(now: Date = .now) -> String {\n        let seconds = abs(now.timeIntervalSince(self))\n        if seconds < 15 {\n            return \"just now\"\n        }\n        return RelativeTimeFormatters.full.localizedString(for: self, relativeTo: now)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/DisplayLink.swift",
    "content": "import AppKit\nimport CoreVideo\nimport Observation\nimport QuartzCore\n\n/// Minimal display link driver using NSScreen.displayLink on macOS 15+,\n/// and CVDisplayLink on macOS 14.\n/// Publishes ticks on the main thread at the requested frame rate.\n@MainActor\n@Observable\nfinal class DisplayLinkDriver {\n    // Published counter used to drive SwiftUI updates.\n    var tick: Int = 0\n    private var displayLink: CADisplayLink?\n    private var cvDisplayLink: CVDisplayLink?\n    private var targetInterval: CFTimeInterval = 1.0 / 60.0\n    private var lastTickTimestamp: CFTimeInterval = 0\n    private let onTick: (() -> Void)?\n\n    init(onTick: (() -> Void)? = nil) {\n        self.onTick = onTick\n    }\n\n    func start(fps: Double = 12) {\n        guard self.displayLink == nil, self.cvDisplayLink == nil else { return }\n        let clampedFps = max(fps, 1)\n        self.targetInterval = 1.0 / clampedFps\n        self.lastTickTimestamp = 0\n        if #available(macOS 15, *), let screen = NSScreen.main {\n            // NSScreen.displayLink is macOS 15+ only.\n            let displayLink = screen.displayLink(target: self, selector: #selector(self.step))\n            let rate = Float(clampedFps)\n            displayLink.preferredFrameRateRange = CAFrameRateRange(\n                minimum: rate,\n                maximum: rate,\n                preferred: rate)\n            displayLink.add(to: .main, forMode: .common)\n            self.displayLink = displayLink\n        } else {\n            self.startCVDisplayLink()\n        }\n    }\n\n    func stop() {\n        self.displayLink?.invalidate()\n        self.displayLink = nil\n        if let cvDisplayLink = self.cvDisplayLink {\n            CVDisplayLinkStop(cvDisplayLink)\n        }\n        self.cvDisplayLink = nil\n    }\n\n    @objc private func step(_: AnyObject) {\n        self.handleTick()\n    }\n\n    private func handleTick() {\n        let now = CACurrentMediaTime()\n        if self.lastTickTimestamp > 0, now - self.lastTickTimestamp < self.targetInterval {\n            return\n        }\n        self.lastTickTimestamp = now\n        // Safe on main runloop; drives SwiftUI updates.\n        self.tick &+= 1\n        self.onTick?()\n    }\n\n    private func startCVDisplayLink() {\n        var link: CVDisplayLink?\n        if CVDisplayLinkCreateWithActiveCGDisplays(&link) != kCVReturnSuccess {\n            return\n        }\n        guard let link else { return }\n        let callback: CVDisplayLinkOutputCallback = { _, _, _, _, _, userInfo in\n            guard let userInfo else { return kCVReturnSuccess }\n            let driver = Unmanaged<DisplayLinkDriver>.fromOpaque(userInfo).takeUnretainedValue()\n            driver.scheduleTick()\n            return kCVReturnSuccess\n        }\n        CVDisplayLinkSetOutputCallback(link, callback, Unmanaged.passUnretained(self).toOpaque())\n        CVDisplayLinkStart(link)\n        self.cvDisplayLink = link\n    }\n\n    private nonisolated func scheduleTick() {\n        Task { @MainActor [weak self] in\n            self?.handleTick()\n        }\n    }\n\n    deinit {\n        Task { @MainActor [weak self] in\n            self?.stop()\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/GeminiLoginRunner.swift",
    "content": "import AppKit\nimport CodexBarCore\nimport Foundation\n\nenum GeminiLoginRunner {\n    private static let geminiConfigDir = FileManager.default.homeDirectoryForCurrentUser\n        .appendingPathComponent(\".gemini\")\n    private static let credentialsFile = \"oauth_creds.json\"\n\n    private static func clearCredentials() {\n        let fm = FileManager.default\n        let filesToDelete = [credentialsFile, \"google_accounts.json\"]\n        for file in filesToDelete {\n            let path = self.geminiConfigDir.appendingPathComponent(file)\n            try? fm.removeItem(at: path)\n        }\n    }\n\n    struct Result {\n        enum Outcome {\n            case success\n            case missingBinary\n            case launchFailed(String)\n        }\n\n        let outcome: Outcome\n    }\n\n    static func run(onCredentialsCreated: (@Sendable () -> Void)? = nil) async -> Result {\n        await Task(priority: .userInitiated) {\n            let env = ProcessInfo.processInfo.environment\n            guard let binary = BinaryLocator.resolveGeminiBinary(\n                env: env,\n                loginPATH: LoginShellPathCache.shared.current)\n            else {\n                return Result(outcome: .missingBinary)\n            }\n\n            // Clear existing credentials before auth (enables clean account switch)\n            Self.clearCredentials()\n\n            // Start watching for credentials file to be created\n            if let callback = onCredentialsCreated {\n                Self.watchForCredentials(callback: callback)\n            }\n\n            // Create a temporary shell script that runs gemini (auto-prompts for auth when no creds)\n            let scriptContent = \"\"\"\n            #!/bin/bash\n            cd ~\n            \"\\(binary)\"\n            \"\"\"\n\n            let tempDir = FileManager.default.temporaryDirectory\n            let scriptURL = tempDir.appendingPathComponent(\"gemini_login_\\(UUID().uuidString).command\")\n\n            do {\n                try scriptContent.write(to: scriptURL, atomically: true, encoding: .utf8)\n                try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: scriptURL.path)\n\n                let config = NSWorkspace.OpenConfiguration()\n                config.activates = true\n                try await NSWorkspace.shared.open(scriptURL, configuration: config)\n\n                // Clean up script after Terminal has time to read it\n                let scriptPath = scriptURL.path\n                DispatchQueue.global().asyncAfter(deadline: .now() + 10) {\n                    try? FileManager.default.removeItem(atPath: scriptPath)\n                }\n\n                return Result(outcome: .success)\n            } catch {\n                return Result(outcome: .launchFailed(error.localizedDescription))\n            }\n        }.value\n    }\n\n    /// Watch for credentials file to be created, then call callback once\n    private static func watchForCredentials(callback: @escaping @Sendable () -> Void, timeout: TimeInterval = 300) {\n        let credsPath = self.geminiConfigDir.appendingPathComponent(self.credentialsFile).path\n\n        DispatchQueue.global(qos: .utility).async {\n            let startTime = Date()\n            while Date().timeIntervalSince(startTime) < timeout {\n                if FileManager.default.fileExists(atPath: credsPath) {\n                    // Small delay to ensure file is fully written\n                    Thread.sleep(forTimeInterval: 0.5)\n                    callback()\n                    return\n                }\n                Thread.sleep(forTimeInterval: 1.0)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/HiddenWindowView.swift",
    "content": "import SwiftUI\n\nstruct HiddenWindowView: View {\n    @Environment(\\.openSettings) private var openSettings\n\n    var body: some View {\n        Color.clear\n            .frame(width: 20, height: 20)\n            .onReceive(NotificationCenter.default.publisher(for: .codexbarOpenSettings)) { _ in\n                Task { @MainActor in\n                    self.openSettings()\n                }\n            }\n            .task {\n                // Migrate keychain items to reduce permission prompts during development (runs off main thread)\n                await Task.detached(priority: .userInitiated) {\n                    KeychainMigration.migrateIfNeeded()\n                }.value\n            }\n            .onAppear {\n                if let window = NSApp.windows.first(where: { $0.title == \"CodexBarLifecycleKeepalive\" }) {\n                    // Make the keepalive window truly invisible and non-interactive.\n                    window.styleMask = [.borderless]\n                    window.collectionBehavior = [.auxiliary, .ignoresCycle, .transient, .canJoinAllSpaces]\n                    window.isExcludedFromWindowsMenu = true\n                    window.level = .floating\n                    window.isOpaque = false\n                    window.alphaValue = 0\n                    window.backgroundColor = .clear\n                    window.hasShadow = false\n                    window.ignoresMouseEvents = true\n                    window.canHide = false\n                    window.setContentSize(NSSize(width: 1, height: 1))\n                    window.setFrameOrigin(NSPoint(x: -5000, y: -5000))\n                }\n            }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/HistoricalUsagePace.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nenum HistoricalUsageWindowKind: String, Codable {\n    case secondary\n}\n\nenum HistoricalUsageRecordSource: String, Codable {\n    case live\n    case backfill\n}\n\nstruct HistoricalUsageRecord: Codable {\n    let v: Int\n    let provider: UsageProvider\n    let windowKind: HistoricalUsageWindowKind\n    let source: HistoricalUsageRecordSource\n    let accountKey: String?\n    let sampledAt: Date\n    let usedPercent: Double\n    let resetsAt: Date\n    let windowMinutes: Int\n\n    init(\n        v: Int,\n        provider: UsageProvider,\n        windowKind: HistoricalUsageWindowKind,\n        source: HistoricalUsageRecordSource,\n        accountKey: String?,\n        sampledAt: Date,\n        usedPercent: Double,\n        resetsAt: Date,\n        windowMinutes: Int)\n    {\n        self.v = v\n        self.provider = provider\n        self.windowKind = windowKind\n        self.source = source\n        self.accountKey = accountKey\n        self.sampledAt = sampledAt\n        self.usedPercent = usedPercent\n        self.resetsAt = resetsAt\n        self.windowMinutes = windowMinutes\n    }\n\n    init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        self.v = try container.decodeIfPresent(Int.self, forKey: .v) ?? 1\n        self.provider = try container.decode(UsageProvider.self, forKey: .provider)\n        self.windowKind = try container.decode(HistoricalUsageWindowKind.self, forKey: .windowKind)\n        self.source = try container.decodeIfPresent(HistoricalUsageRecordSource.self, forKey: .source) ?? .live\n        self.accountKey = try container.decodeIfPresent(String.self, forKey: .accountKey)\n        self.sampledAt = try container.decode(Date.self, forKey: .sampledAt)\n        self.usedPercent = try container.decode(Double.self, forKey: .usedPercent)\n        self.resetsAt = try container.decode(Date.self, forKey: .resetsAt)\n        self.windowMinutes = try container.decode(Int.self, forKey: .windowMinutes)\n    }\n}\n\nstruct HistoricalWeekProfile {\n    let resetsAt: Date\n    let windowMinutes: Int\n    let curve: [Double]\n}\n\nstruct CodexHistoricalDataset {\n    static let gridPointCount = 169\n    let weeks: [HistoricalWeekProfile]\n}\n\nactor HistoricalUsageHistoryStore {\n    private static let schemaVersion = 1\n    private static let writeInterval: TimeInterval = 30 * 60\n    private static let writeDeltaThreshold: Double = 1\n    private static let retentionDays: TimeInterval = 56 * 24 * 60 * 60\n    private static let minimumWeekSamples = 6\n    private static let boundaryCoverageWindow: TimeInterval = 24 * 60 * 60\n    private static let backfillWindowCapWeeks = 8\n    private static let backfillCalibrationMinimumUsedPercent = 1.0\n    private static let backfillCalibrationMinimumCredits = 0.001\n    private static let backfillSampleFractions: [Double] = (0...14).map { Double($0) / 14.0 }\n    private static let coverageTolerance: TimeInterval = 16 * 60 * 60\n    private static let resetBucketSeconds: TimeInterval = 60\n\n    private let fileURL: URL\n    private var records: [HistoricalUsageRecord] = []\n    private var loaded = false\n\n    init(fileURL: URL? = nil) {\n        self.fileURL = fileURL ?? HistoricalUsageHistoryStore.defaultFileURL()\n    }\n\n    func loadCodexDataset(accountKey: String?) -> CodexHistoricalDataset? {\n        self.ensureLoaded()\n        return self.buildDataset(accountKey: accountKey)\n    }\n\n    func recordCodexWeekly(\n        window: RateWindow,\n        sampledAt: Date = .init(),\n        accountKey: String?) -> CodexHistoricalDataset?\n    {\n        guard let rawResetsAt = window.resetsAt else { return self.loadCodexDataset(accountKey: accountKey) }\n        guard let windowMinutes = window.windowMinutes, windowMinutes > 0 else {\n            return self.loadCodexDataset(accountKey: accountKey)\n        }\n        self.ensureLoaded()\n        let resetsAt = Self.normalizeReset(rawResetsAt)\n\n        let sample = HistoricalUsageRecord(\n            v: Self.schemaVersion,\n            provider: .codex,\n            windowKind: .secondary,\n            source: .live,\n            accountKey: accountKey,\n            sampledAt: sampledAt,\n            usedPercent: Self.clamp(window.usedPercent, lower: 0, upper: 100),\n            resetsAt: resetsAt,\n            windowMinutes: windowMinutes)\n\n        if !self.shouldAccept(sample) {\n            return self.buildDataset(accountKey: accountKey)\n        }\n\n        self.records.append(sample)\n        self.pruneOldRecords(now: sampledAt)\n        self.records.sort { lhs, rhs in\n            if lhs.sampledAt == rhs.sampledAt {\n                if lhs.resetsAt == rhs.resetsAt {\n                    return lhs.usedPercent < rhs.usedPercent\n                }\n                return lhs.resetsAt < rhs.resetsAt\n            }\n            return lhs.sampledAt < rhs.sampledAt\n        }\n        self.persist()\n        return self.buildDataset(accountKey: accountKey)\n    }\n\n    func backfillCodexWeeklyFromUsageBreakdown(\n        _ breakdown: [OpenAIDashboardDailyBreakdown],\n        referenceWindow: RateWindow,\n        now: Date = .init(),\n        accountKey: String?) -> CodexHistoricalDataset?\n    {\n        self.ensureLoaded()\n        let existingDataset = self.buildDataset(accountKey: accountKey)\n\n        guard let rawResetsAt = referenceWindow.resetsAt else { return existingDataset }\n        guard let windowMinutes = referenceWindow.windowMinutes, windowMinutes > 0 else { return existingDataset }\n        let resetsAt = Self.normalizeReset(rawResetsAt)\n\n        let duration = TimeInterval(windowMinutes) * 60\n        guard duration > 0 else { return existingDataset }\n\n        let windowStart = resetsAt.addingTimeInterval(-duration)\n        let calibrationEnd = Self.clampDate(now, lower: windowStart, upper: resetsAt)\n        let dayUsages = Self.parseDayUsages(\n            from: breakdown,\n            asOf: calibrationEnd,\n            fillingFrom: windowStart)\n        guard !dayUsages.isEmpty else { return existingDataset }\n        guard let coverageStart = dayUsages.first?.start, let coverageEnd = dayUsages.last?.end else {\n            return existingDataset\n        }\n        guard coverageStart <= windowStart.addingTimeInterval(Self.coverageTolerance) else {\n            return existingDataset\n        }\n        guard coverageEnd >= calibrationEnd.addingTimeInterval(-Self.coverageTolerance) else {\n            return existingDataset\n        }\n\n        let currentUsedPercent = Self.clamp(referenceWindow.usedPercent, lower: 0, upper: 100)\n        guard currentUsedPercent >= Self.backfillCalibrationMinimumUsedPercent else { return existingDataset }\n\n        let currentCredits = Self.creditsUsed(\n            from: dayUsages,\n            between: windowStart,\n            and: calibrationEnd)\n        guard currentCredits > Self.backfillCalibrationMinimumCredits else { return existingDataset }\n\n        let estimatedCreditsAtLimit = currentCredits / (currentUsedPercent / 100)\n        guard estimatedCreditsAtLimit.isFinite, estimatedCreditsAtLimit > Self.backfillCalibrationMinimumCredits else {\n            return existingDataset\n        }\n\n        struct RecordKey: Hashable {\n            let resetsAt: Date\n            let sampledAt: Date\n            let windowMinutes: Int\n            let accountKey: String?\n        }\n\n        var synthesized: [HistoricalUsageRecord] = []\n        synthesized.reserveCapacity(Self.backfillWindowCapWeeks * Self.backfillSampleFractions.count)\n\n        for weeksBack in 1...Self.backfillWindowCapWeeks {\n            let reset = Self.normalizeReset(resetsAt.addingTimeInterval(-duration * Double(weeksBack)))\n            let start = reset.addingTimeInterval(-duration)\n            guard start >= coverageStart.addingTimeInterval(-Self.coverageTolerance),\n                  reset <= coverageEnd.addingTimeInterval(Self.coverageTolerance)\n            else {\n                continue\n            }\n\n            let existingForWeek = self.records.filter {\n                $0.provider == .codex &&\n                    $0.windowKind == .secondary &&\n                    $0.windowMinutes == windowMinutes &&\n                    $0.accountKey == accountKey &&\n                    $0.resetsAt == reset\n            }\n            if Self.isCompleteWeek(samples: existingForWeek, windowStart: start, resetsAt: reset) {\n                continue\n            }\n            var existingRecordKeys = Set(existingForWeek.map {\n                RecordKey(\n                    resetsAt: $0.resetsAt,\n                    sampledAt: $0.sampledAt,\n                    windowMinutes: $0.windowMinutes,\n                    accountKey: $0.accountKey)\n            })\n\n            let weekCredits = Self.creditsUsed(from: dayUsages, between: start, and: reset)\n            guard weekCredits > Self.backfillCalibrationMinimumCredits else { continue }\n\n            for fraction in Self.backfillSampleFractions {\n                let sampledAt = start.addingTimeInterval(duration * fraction)\n                let recordKey = RecordKey(\n                    resetsAt: reset,\n                    sampledAt: sampledAt,\n                    windowMinutes: windowMinutes,\n                    accountKey: accountKey)\n                guard !existingRecordKeys.contains(recordKey) else { continue }\n                let cumulativeCredits = Self.creditsUsed(from: dayUsages, between: start, and: sampledAt)\n                let usedPercent = Self.clamp((cumulativeCredits / estimatedCreditsAtLimit) * 100, lower: 0, upper: 100)\n                synthesized.append(HistoricalUsageRecord(\n                    v: Self.schemaVersion,\n                    provider: .codex,\n                    windowKind: .secondary,\n                    source: .backfill,\n                    accountKey: accountKey,\n                    sampledAt: sampledAt,\n                    usedPercent: usedPercent,\n                    resetsAt: reset,\n                    windowMinutes: windowMinutes))\n                existingRecordKeys.insert(recordKey)\n            }\n        }\n\n        guard !synthesized.isEmpty else { return existingDataset }\n        self.records.append(contentsOf: synthesized)\n        self.pruneOldRecords(now: now)\n        self.records.sort { lhs, rhs in\n            if lhs.sampledAt == rhs.sampledAt {\n                if lhs.resetsAt == rhs.resetsAt {\n                    return lhs.usedPercent < rhs.usedPercent\n                }\n                return lhs.resetsAt < rhs.resetsAt\n            }\n            return lhs.sampledAt < rhs.sampledAt\n        }\n        self.persist()\n        return self.buildDataset(accountKey: accountKey)\n    }\n\n    private func shouldAccept(_ sample: HistoricalUsageRecord) -> Bool {\n        guard let prior = self.records\n            .last(where: {\n                $0.provider == sample.provider &&\n                    $0.windowKind == sample.windowKind &&\n                    $0.accountKey == sample.accountKey &&\n                    $0.windowMinutes == sample.windowMinutes\n            })\n        else {\n            return true\n        }\n\n        if prior.resetsAt != sample.resetsAt { return true }\n        if sample.sampledAt.timeIntervalSince(prior.sampledAt) >= Self.writeInterval { return true }\n        if abs(sample.usedPercent - prior.usedPercent) >= Self.writeDeltaThreshold { return true }\n        return false\n    }\n\n    private func pruneOldRecords(now: Date) {\n        let cutoff = now.addingTimeInterval(-Self.retentionDays)\n        self.records.removeAll { $0.sampledAt < cutoff }\n    }\n\n    private func ensureLoaded() {\n        guard !self.loaded else { return }\n        self.loaded = true\n        self.records = self.readRecordsFromDisk()\n        self.pruneOldRecords(now: .init())\n    }\n\n    private func readRecordsFromDisk() -> [HistoricalUsageRecord] {\n        guard let data = try? Data(contentsOf: self.fileURL), !data.isEmpty else { return [] }\n        guard let text = String(data: data, encoding: .utf8) else { return [] }\n\n        let decoder = JSONDecoder()\n        decoder.dateDecodingStrategy = .iso8601\n        var decoded: [HistoricalUsageRecord] = []\n        decoded.reserveCapacity(text.count / 80)\n\n        for rawLine in text.split(separator: \"\\n\", omittingEmptySubsequences: true) {\n            let line = String(rawLine).trimmingCharacters(in: .whitespacesAndNewlines)\n            guard !line.isEmpty, let lineData = line.data(using: .utf8) else { continue }\n            guard var record = try? decoder.decode(HistoricalUsageRecord.self, from: lineData) else { continue }\n            record = HistoricalUsageRecord(\n                v: record.v,\n                provider: record.provider,\n                windowKind: record.windowKind,\n                source: record.source,\n                accountKey: record.accountKey?.isEmpty == false ? record.accountKey : nil,\n                sampledAt: record.sampledAt,\n                usedPercent: Self.clamp(record.usedPercent, lower: 0, upper: 100),\n                resetsAt: Self.normalizeReset(record.resetsAt),\n                windowMinutes: record.windowMinutes)\n            decoded.append(record)\n        }\n        return decoded\n    }\n\n    private func persist() {\n        let encoder = JSONEncoder()\n        encoder.dateEncodingStrategy = .iso8601\n        encoder.outputFormatting = [.sortedKeys]\n\n        var lines: [String] = []\n        lines.reserveCapacity(self.records.count)\n        for record in self.records {\n            guard let data = try? encoder.encode(record),\n                  let line = String(data: data, encoding: .utf8)\n            else {\n                continue\n            }\n            lines.append(line)\n        }\n\n        let payload = (lines.joined(separator: \"\\n\") + \"\\n\").data(using: .utf8) ?? Data()\n        let directory = self.fileURL.deletingLastPathComponent()\n        do {\n            try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)\n            try payload.write(to: self.fileURL, options: [.atomic])\n        } catch {\n            // Best-effort cache file; ignore write failures.\n        }\n    }\n\n    private func buildDataset(accountKey: String?) -> CodexHistoricalDataset? {\n        struct WeekKey: Hashable {\n            let resetsAt: Date\n            let windowMinutes: Int\n        }\n\n        let scoped = self.records\n            .filter { record in\n                guard record.provider == .codex, record.windowKind == .secondary, record.windowMinutes > 0 else {\n                    return false\n                }\n                if let accountKey {\n                    return record.accountKey == accountKey\n                }\n                return record.accountKey == nil\n            }\n        if scoped.isEmpty { return nil }\n\n        let grouped = Dictionary(grouping: scoped) {\n            WeekKey(resetsAt: $0.resetsAt, windowMinutes: $0.windowMinutes)\n        }\n\n        var weeks: [HistoricalWeekProfile] = []\n        weeks.reserveCapacity(grouped.count)\n\n        for (key, samples) in grouped {\n            let duration = TimeInterval(key.windowMinutes) * 60\n            guard duration > 0 else { continue }\n            let windowStart = key.resetsAt.addingTimeInterval(-duration)\n            guard Self.isCompleteWeek(samples: samples, windowStart: windowStart, resetsAt: key.resetsAt) else {\n                continue\n            }\n\n            guard let curve = Self.reconstructWeekCurve(\n                samples: samples,\n                windowStart: windowStart,\n                windowDuration: duration,\n                gridPointCount: CodexHistoricalDataset.gridPointCount)\n            else {\n                continue\n            }\n\n            weeks.append(HistoricalWeekProfile(\n                resetsAt: key.resetsAt,\n                windowMinutes: key.windowMinutes,\n                curve: curve))\n        }\n\n        weeks.sort { $0.resetsAt < $1.resetsAt }\n        if weeks.isEmpty { return nil }\n        return CodexHistoricalDataset(weeks: weeks)\n    }\n\n    private static func reconstructWeekCurve(\n        samples: [HistoricalUsageRecord],\n        windowStart: Date,\n        windowDuration: TimeInterval,\n        gridPointCount: Int) -> [Double]?\n    {\n        guard gridPointCount >= 2 else { return nil }\n\n        var points = samples.map { sample -> (u: Double, value: Double) in\n            let offset = sample.sampledAt.timeIntervalSince(windowStart)\n            let u = Self.clamp(offset / windowDuration, lower: 0, upper: 1)\n            return (u: u, value: Self.clamp(sample.usedPercent, lower: 0, upper: 100))\n        }\n        points.sort { lhs, rhs in\n            if lhs.u == rhs.u {\n                return lhs.value < rhs.value\n            }\n            return lhs.u < rhs.u\n        }\n\n        guard !points.isEmpty else { return nil }\n\n        // Enforce monotonicity on observed samples before interpolation.\n        var monotonePoints: [(u: Double, value: Double)] = []\n        monotonePoints.reserveCapacity(points.count)\n        var runningMax = 0.0\n        for point in points {\n            runningMax = max(runningMax, point.value)\n            monotonePoints.append((u: point.u, value: runningMax))\n        }\n\n        // Anchor reconstructed curves to reset start and end-of-week plateau.\n        let endValue = monotonePoints.last?.value ?? 0\n        monotonePoints.append((u: 0, value: 0))\n        monotonePoints.append((u: 1, value: endValue))\n        monotonePoints.sort { lhs, rhs in\n            if lhs.u == rhs.u {\n                return lhs.value < rhs.value\n            }\n            return lhs.u < rhs.u\n        }\n        runningMax = 0\n        for index in monotonePoints.indices {\n            runningMax = max(runningMax, monotonePoints[index].value)\n            monotonePoints[index].value = runningMax\n        }\n\n        var curve = Array(repeating: 0.0, count: gridPointCount)\n        let first = monotonePoints[0]\n        let last = monotonePoints[monotonePoints.count - 1]\n\n        var upperIndex = 1\n        let denominator = Double(gridPointCount - 1)\n\n        for index in 0..<gridPointCount {\n            let u = Double(index) / denominator\n            if u <= first.u {\n                curve[index] = first.value\n                continue\n            }\n            if u >= last.u {\n                curve[index] = last.value\n                continue\n            }\n\n            while upperIndex < monotonePoints.count, monotonePoints[upperIndex].u < u {\n                upperIndex += 1\n            }\n\n            let hi = monotonePoints[min(upperIndex, monotonePoints.count - 1)]\n            let lo = monotonePoints[max(0, upperIndex - 1)]\n            if hi.u <= lo.u {\n                curve[index] = max(lo.value, hi.value)\n                continue\n            }\n\n            let ratio = Self.clamp((u - lo.u) / (hi.u - lo.u), lower: 0, upper: 1)\n            curve[index] = lo.value + (hi.value - lo.value) * ratio\n        }\n\n        // Re-enforce monotonicity on reconstructed grid.\n        var curveMax = 0.0\n        for index in curve.indices {\n            curve[index] = Self.clamp(curve[index], lower: 0, upper: 100)\n            curveMax = max(curveMax, curve[index])\n            curve[index] = curveMax\n        }\n        return curve\n    }\n\n    private static func isCompleteWeek(samples: [HistoricalUsageRecord], windowStart: Date, resetsAt: Date) -> Bool {\n        guard samples.count >= self.minimumWeekSamples else { return false }\n        let startBoundary = windowStart.addingTimeInterval(Self.boundaryCoverageWindow)\n        let endBoundary = resetsAt.addingTimeInterval(-Self.boundaryCoverageWindow)\n        let hasStartCoverage = samples.contains { sample in\n            sample.sampledAt >= windowStart && sample.sampledAt <= startBoundary\n        }\n        let hasEndCoverage = samples.contains { sample in\n            sample.sampledAt >= endBoundary && sample.sampledAt <= resetsAt\n        }\n        return hasStartCoverage && hasEndCoverage\n    }\n\n    private struct DayUsage {\n        let start: Date\n        let end: Date\n        let creditsUsed: Double\n    }\n\n    private static func parseDayUsages(\n        from breakdown: [OpenAIDashboardDailyBreakdown],\n        asOf: Date,\n        fillingFrom expectedCoverageStart: Date? = nil) -> [DayUsage]\n    {\n        var creditsByStart: [Date: Double] = [:]\n        creditsByStart.reserveCapacity(breakdown.count)\n\n        for day in breakdown {\n            guard let dayStart = Self.dayStart(for: day.day) else { continue }\n            creditsByStart[dayStart, default: 0] += max(0, day.totalCreditsUsed)\n        }\n\n        let calendar = Self.gregorianCalendar()\n        var dayUsages: [DayUsage] = []\n        dayUsages.reserveCapacity(creditsByStart.count)\n        for (dayStart, credits) in creditsByStart {\n            guard let nominalEnd = calendar.date(byAdding: .day, value: 1, to: dayStart) else { continue }\n            let effectiveEnd: Date = if dayStart <= asOf, asOf < nominalEnd {\n                asOf\n            } else {\n                nominalEnd\n            }\n            guard effectiveEnd > dayStart else { continue }\n            dayUsages.append(DayUsage(start: dayStart, end: effectiveEnd, creditsUsed: credits))\n        }\n\n        dayUsages.sort { lhs, rhs in lhs.start < rhs.start }\n        return Self.fillMissingZeroUsageDays(\n            in: dayUsages,\n            through: asOf,\n            fillingFrom: expectedCoverageStart)\n    }\n\n    private static func fillMissingZeroUsageDays(\n        in dayUsages: [DayUsage],\n        through asOf: Date,\n        fillingFrom expectedCoverageStart: Date? = nil) -> [DayUsage]\n    {\n        guard let firstStart = dayUsages.first?.start else { return [] }\n\n        let calendar = Self.gregorianCalendar()\n        let fillStart: Date = if let expectedCoverageStart {\n            min(firstStart, calendar.startOfDay(for: expectedCoverageStart))\n        } else {\n            firstStart\n        }\n        let finalDayStart = calendar.startOfDay(for: asOf)\n        guard fillStart <= finalDayStart else { return dayUsages }\n\n        let creditsByStart = Dictionary(uniqueKeysWithValues: dayUsages.map { ($0.start, $0.creditsUsed) })\n        let daySpan = max(0, calendar.dateComponents([.day], from: fillStart, to: finalDayStart).day ?? 0)\n        var filled: [DayUsage] = []\n        filled.reserveCapacity(daySpan + 1)\n\n        var cursor = fillStart\n        while cursor <= finalDayStart {\n            guard let nominalEnd = calendar.date(byAdding: .day, value: 1, to: cursor) else { break }\n            let effectiveEnd: Date = if cursor <= asOf, asOf < nominalEnd {\n                asOf\n            } else {\n                nominalEnd\n            }\n            guard effectiveEnd > cursor else { break }\n            filled.append(DayUsage(\n                start: cursor,\n                end: effectiveEnd,\n                creditsUsed: creditsByStart[cursor] ?? 0))\n            guard let next = calendar.date(byAdding: .day, value: 1, to: cursor) else { break }\n            cursor = next\n        }\n\n        return filled\n    }\n\n    private static func dayStart(for key: String) -> Date? {\n        let components = key.split(separator: \"-\", omittingEmptySubsequences: true)\n        guard components.count == 3,\n              let year = Int(components[0]),\n              let month = Int(components[1]),\n              let day = Int(components[2])\n        else {\n            return nil\n        }\n\n        let calendar = Self.gregorianCalendar()\n        var dateComponents = DateComponents()\n        dateComponents.calendar = calendar\n        dateComponents.timeZone = calendar.timeZone\n        dateComponents.year = year\n        dateComponents.month = month\n        dateComponents.day = day\n        dateComponents.hour = 0\n        dateComponents.minute = 0\n        dateComponents.second = 0\n        return dateComponents.date\n    }\n\n    private static func creditsUsed(from dayUsages: [DayUsage], between start: Date, and end: Date) -> Double {\n        guard end > start else { return 0 }\n        var total = 0.0\n        for day in dayUsages {\n            if day.end <= start { continue }\n            if day.start >= end { break }\n            let overlapStart = max(day.start, start)\n            let overlapEnd = min(day.end, end)\n            guard overlapEnd > overlapStart else { continue }\n\n            let dayDuration = day.end.timeIntervalSince(day.start)\n            guard dayDuration > 0 else { continue }\n            let overlap = overlapEnd.timeIntervalSince(overlapStart)\n            total += day.creditsUsed * (overlap / dayDuration)\n        }\n        return max(0, total)\n    }\n\n    nonisolated static func defaultFileURL() -> URL {\n        let root = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first\n            ?? FileManager.default.homeDirectoryForCurrentUser\n        return root\n            .appendingPathComponent(\"CodexBar\", isDirectory: true)\n            .appendingPathComponent(\"usage-history.jsonl\", isDirectory: false)\n    }\n\n    private nonisolated static func clamp(_ value: Double, lower: Double, upper: Double) -> Double {\n        min(upper, max(lower, value))\n    }\n\n    private nonisolated static func clampDate(_ value: Date, lower: Date, upper: Date) -> Date {\n        min(upper, max(lower, value))\n    }\n\n    private nonisolated static func normalizeReset(_ value: Date) -> Date {\n        let bucket = Self.resetBucketSeconds\n        guard bucket > 0 else { return value }\n        let rounded = (value.timeIntervalSinceReferenceDate / bucket).rounded() * bucket\n        return Date(timeIntervalSinceReferenceDate: rounded)\n    }\n\n    private nonisolated static func gregorianCalendar() -> Calendar {\n        var calendar = Calendar(identifier: .gregorian)\n        calendar.timeZone = TimeZone.current\n        return calendar\n    }\n\n    #if DEBUG\n    nonisolated static func _dayStartForTesting(_ key: String) -> Date? {\n        self.dayStart(for: key)\n    }\n\n    nonisolated static func _creditsUsedForTesting(\n        breakdown: [OpenAIDashboardDailyBreakdown],\n        asOf: Date,\n        start: Date,\n        end: Date) -> Double\n    {\n        let dayUsages = Self.parseDayUsages(from: breakdown, asOf: asOf)\n        return Self.creditsUsed(from: dayUsages, between: start, and: end)\n    }\n    #endif\n}\n\nenum CodexHistoricalPaceEvaluator {\n    static let minimumCompleteWeeksForHistorical = 3\n    static let minimumWeeksForRisk = 5\n    private static let recencyTauWeeks: Double = 3\n    private static let epsilon: Double = 1e-9\n    private static let resetBucketSeconds: TimeInterval = 60\n\n    static func evaluate(window: RateWindow, now: Date, dataset: CodexHistoricalDataset?) -> UsagePace? {\n        guard let dataset else { return nil }\n        guard let resetsAt = window.resetsAt else { return nil }\n        let minutes = window.windowMinutes ?? 10080\n        guard minutes > 0 else { return nil }\n\n        let duration = TimeInterval(minutes) * 60\n        guard duration > 0 else { return nil }\n\n        let timeUntilReset = resetsAt.timeIntervalSince(now)\n        guard timeUntilReset > 0, timeUntilReset <= duration else { return nil }\n        let normalizedResetsAt = Self.normalizeReset(resetsAt)\n\n        let elapsed = Self.clamp(duration - timeUntilReset, lower: 0, upper: duration)\n        let actual = Self.clamp(window.usedPercent, lower: 0, upper: 100)\n        if elapsed == 0, actual > 0 { return nil }\n\n        let uNow = Self.clamp(elapsed / duration, lower: 0, upper: 1)\n        let scopedWeeks = dataset.weeks.filter { week in\n            week.windowMinutes == minutes && week.resetsAt < normalizedResetsAt\n        }\n        guard scopedWeeks.count >= Self.minimumCompleteWeeksForHistorical else { return nil }\n\n        let weightedWeeks = scopedWeeks.map { week in\n            let ageWeeks = Self.clamp(\n                normalizedResetsAt.timeIntervalSince(week.resetsAt) / duration,\n                lower: 0,\n                upper: Double.greatestFiniteMagnitude)\n            let weight = exp(-ageWeeks / Self.recencyTauWeeks)\n            return (week: week, weight: weight)\n        }\n        let totalWeight = weightedWeeks.reduce(0.0) { $0 + $1.weight }\n        guard totalWeight > Self.epsilon else { return nil }\n\n        let totalWeightSquared = weightedWeeks.reduce(0.0) { $0 + ($1.weight * $1.weight) }\n        let nEff = totalWeightSquared > Self.epsilon ? (totalWeight * totalWeight) / totalWeightSquared : 0\n        let lambda = Self.clamp((nEff - 2) / 6, lower: 0, upper: 1)\n\n        let gridCount = CodexHistoricalDataset.gridPointCount\n        let denominator = Double(gridCount - 1)\n        var expectedCurve = Array(repeating: 0.0, count: gridCount)\n        for index in 0..<gridCount {\n            let u = Double(index) / denominator\n            let values = weightedWeeks.map { $0.week.curve[index] }\n            let weights = weightedWeeks.map(\\.weight)\n            let historicalMedian = Self.weightedMedian(values: values, weights: weights)\n            let linearBaseline = 100 * u\n            expectedCurve[index] = Self.clamp(\n                (lambda * historicalMedian) + ((1 - lambda) * linearBaseline),\n                lower: 0,\n                upper: 100)\n        }\n\n        // Expected cumulative usage should be monotone.\n        var runningExpected = 0.0\n        for index in expectedCurve.indices {\n            runningExpected = max(runningExpected, expectedCurve[index])\n            expectedCurve[index] = runningExpected\n        }\n\n        let expectedNow = Self.interpolate(curve: expectedCurve, at: uNow)\n\n        var weightedRunOutMass = 0.0\n        var crossingCandidates: [(etaSeconds: TimeInterval, weight: Double)] = []\n        crossingCandidates.reserveCapacity(weightedWeeks.count)\n\n        for weighted in weightedWeeks {\n            let week = weighted.week\n            let weight = weighted.weight\n            let weekNow = Self.interpolate(curve: week.curve, at: uNow)\n            let shift = actual - weekNow\n            let shiftedEnd = Self.clamp((week.curve.last ?? 0) + shift, lower: 0, upper: 100)\n            let runOut = shiftedEnd >= 100 - Self.epsilon\n            if runOut {\n                weightedRunOutMass += weight\n                if let crossingU = Self.firstCrossing(\n                    after: uNow,\n                    curve: week.curve,\n                    shift: shift,\n                    actualAtNow: actual)\n                {\n                    let etaSeconds = max(0, (crossingU - uNow) * duration)\n                    crossingCandidates.append((etaSeconds: etaSeconds, weight: weight))\n                }\n            }\n        }\n\n        let smoothedProbability = Self.clamp(\n            (weightedRunOutMass + 0.5) / (totalWeight + 1),\n            lower: 0,\n            upper: 1)\n        let runOutProbability: Double? = scopedWeeks.count >= Self.minimumWeeksForRisk ? smoothedProbability : nil\n\n        var willLastToReset = smoothedProbability < 0.5\n        var etaSeconds: TimeInterval?\n\n        if !willLastToReset {\n            let values = crossingCandidates.map(\\.etaSeconds)\n            let weights = crossingCandidates.map(\\.weight)\n            if values.isEmpty {\n                willLastToReset = true\n            } else {\n                etaSeconds = max(0, Self.weightedMedian(values: values, weights: weights))\n            }\n        }\n\n        return UsagePace.historical(\n            expectedUsedPercent: expectedNow,\n            actualUsedPercent: actual,\n            etaSeconds: etaSeconds,\n            willLastToReset: willLastToReset,\n            runOutProbability: runOutProbability)\n    }\n\n    private static func firstCrossing(\n        after uNow: Double,\n        curve: [Double],\n        shift: Double,\n        actualAtNow: Double) -> Double?\n    {\n        let gridCount = curve.count\n        guard gridCount >= 2 else { return nil }\n\n        let denominator = Double(gridCount - 1)\n        var previousU = uNow\n        var previousValue = actualAtNow\n\n        let startIndex = min(gridCount - 1, max(1, Int(floor(uNow * denominator)) + 1))\n        for index in startIndex..<gridCount {\n            let u = Double(index) / denominator\n            if u <= uNow + Self.epsilon { continue }\n            let value = Self.clamp(curve[index] + shift, lower: 0, upper: 100)\n            if previousValue < 100 - Self.epsilon, value >= 100 - Self.epsilon {\n                let delta = value - previousValue\n                if abs(delta) <= Self.epsilon { return u }\n                let ratio = Self.clamp((100 - previousValue) / delta, lower: 0, upper: 1)\n                return Self.clamp(previousU + ratio * (u - previousU), lower: uNow, upper: 1)\n            }\n            previousU = u\n            previousValue = value\n        }\n        return nil\n    }\n\n    private static func interpolate(curve: [Double], at u: Double) -> Double {\n        guard !curve.isEmpty else { return 0 }\n        if curve.count == 1 { return curve[0] }\n\n        let clipped = Self.clamp(u, lower: 0, upper: 1)\n        let scaled = clipped * Double(curve.count - 1)\n        let lower = Int(floor(scaled))\n        let upper = min(curve.count - 1, lower + 1)\n        if lower == upper { return curve[lower] }\n        let ratio = scaled - Double(lower)\n        return curve[lower] + ((curve[upper] - curve[lower]) * ratio)\n    }\n\n    private static func weightedMedian(values: [Double], weights: [Double]) -> Double {\n        guard values.count == weights.count, !values.isEmpty else { return 0 }\n        let pairs = zip(values, weights)\n            .map { (value: $0, weight: max(0, $1)) }\n            .sorted { lhs, rhs in lhs.value < rhs.value }\n        let totalWeight = pairs.reduce(0.0) { $0 + $1.weight }\n        if totalWeight <= Self.epsilon {\n            let sortedValues = values.sorted()\n            return sortedValues[sortedValues.count / 2]\n        }\n\n        let threshold = totalWeight / 2\n        var cumulative = 0.0\n        for pair in pairs {\n            cumulative += pair.weight\n            if cumulative >= threshold {\n                return pair.value\n            }\n        }\n        return pairs.last?.value ?? 0\n    }\n\n    private static func clamp(_ value: Double, lower: Double, upper: Double) -> Double {\n        min(upper, max(lower, value))\n    }\n\n    private static func normalizeReset(_ value: Date) -> Date {\n        let bucket = Self.resetBucketSeconds\n        guard bucket > 0 else { return value }\n        let rounded = (value.timeIntervalSinceReferenceDate / bucket).rounded() * bucket\n        return Date(timeIntervalSinceReferenceDate: rounded)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/IconRenderer.swift",
    "content": "import AppKit\nimport CodexBarCore\n\n// swiftlint:disable:next type_body_length\nenum IconRenderer {\n    private static let creditsCap: Double = 1000\n    private static let baseSize = NSSize(width: 18, height: 18)\n    // Render to an 18×18 pt template (36×36 px at 2×) to match the system menu bar size.\n    private static let outputSize = NSSize(width: 18, height: 18)\n    private static let outputScale: CGFloat = 2\n    private static let canvasPx = Int(outputSize.width * outputScale)\n\n    private struct PixelGrid {\n        let scale: CGFloat\n\n        func pt(_ px: Int) -> CGFloat {\n            CGFloat(px) / self.scale\n        }\n\n        func rect(x: Int, y: Int, w: Int, h: Int) -> CGRect {\n            CGRect(x: self.pt(x), y: self.pt(y), width: self.pt(w), height: self.pt(h))\n        }\n\n        func snapDelta(_ value: CGFloat) -> CGFloat {\n            (value * self.scale).rounded() / self.scale\n        }\n    }\n\n    private static let grid = PixelGrid(scale: outputScale)\n\n    private struct IconCacheKey: Hashable {\n        let primary: Int\n        let weekly: Int\n        let credits: Int\n        let stale: Bool\n        let style: Int\n        let indicator: Int\n    }\n\n    private final class IconCacheStore: @unchecked Sendable {\n        private var cache: [IconCacheKey: NSImage] = [:]\n        private var order: [IconCacheKey] = []\n        private let lock = NSLock()\n\n        func cachedIcon(for key: IconCacheKey) -> NSImage? {\n            self.lock.lock()\n            defer { self.lock.unlock() }\n            guard let image = self.cache[key] else { return nil }\n            if let idx = self.order.firstIndex(of: key) {\n                self.order.remove(at: idx)\n                self.order.append(key)\n            }\n            return image\n        }\n\n        func storeIcon(_ image: NSImage, for key: IconCacheKey, limit: Int) {\n            self.lock.lock()\n            defer { self.lock.unlock() }\n            self.cache[key] = image\n            self.order.removeAll { $0 == key }\n            self.order.append(key)\n            while self.order.count > limit {\n                let oldest = self.order.removeFirst()\n                self.cache.removeValue(forKey: oldest)\n            }\n        }\n    }\n\n    private static let iconCacheStore = IconCacheStore()\n    private static let iconCacheLimit = 64\n    private static let morphBucketCount = 200\n    private static let morphCache = MorphCache(limit: 512)\n\n    private final class MorphCache: @unchecked Sendable {\n        private let cache = NSCache<NSNumber, NSImage>()\n\n        init(limit: Int) {\n            self.cache.countLimit = limit\n        }\n\n        func image(for key: NSNumber) -> NSImage? {\n            self.cache.object(forKey: key)\n        }\n\n        func set(_ image: NSImage, for key: NSNumber) {\n            self.cache.setObject(image, forKey: key)\n        }\n    }\n\n    private struct RectPx: Hashable {\n        let x: Int\n        let y: Int\n        let w: Int\n        let h: Int\n\n        var midXPx: Int {\n            self.x + self.w / 2\n        }\n\n        var midYPx: Int {\n            self.y + self.h / 2\n        }\n\n        func rect() -> CGRect {\n            Self.grid.rect(x: self.x, y: self.y, w: self.w, h: self.h)\n        }\n\n        private static let grid = IconRenderer.grid\n    }\n\n    // swiftlint:disable function_body_length\n    static func makeIcon(\n        primaryRemaining: Double?,\n        weeklyRemaining: Double?,\n        creditsRemaining: Double?,\n        stale: Bool,\n        style: IconStyle,\n        blink: CGFloat = 0,\n        wiggle: CGFloat = 0,\n        tilt: CGFloat = 0,\n        statusIndicator: ProviderStatusIndicator = .none) -> NSImage\n    {\n        let shouldCache = blink <= 0.0001 && wiggle <= 0.0001 && tilt <= 0.0001\n        let render = {\n            self.renderImage {\n                // Keep monochrome template icons; Claude uses subtle shape cues only.\n                let baseFill = NSColor.labelColor\n                let trackFillAlpha: CGFloat = stale ? 0.18 : 0.28\n                let trackStrokeAlpha: CGFloat = stale ? 0.28 : 0.44\n                let fillColor = baseFill.withAlphaComponent(stale ? 0.55 : 1.0)\n\n                let barWidthPx = 30 // 15 pt at 2×, uses the slot better without touching edges.\n                let barXPx = (Self.canvasPx - barWidthPx) / 2\n\n                func drawBar(\n                    rectPx: RectPx,\n                    remaining: Double?,\n                    alpha: CGFloat = 1.0,\n                    addNotches: Bool = false,\n                    addFace: Bool = false,\n                    addGeminiTwist: Bool = false,\n                    addAntigravityTwist: Bool = false,\n                    addFactoryTwist: Bool = false,\n                    addWarpTwist: Bool = false,\n                    blink: CGFloat = 0,\n                    drawTrackFill: Bool = true,\n                    warpEyesFilled: Bool = false)\n                {\n                    let rect = rectPx.rect()\n                    // Claude reads better as a blockier critter; Codex stays as a capsule.\n                    // Warp uses small corner radius for rounded rectangle (matching logo style)\n                    let cornerRadiusPx = addNotches ? 0 : (addWarpTwist ? 3 : rectPx.h / 2)\n                    let radius = Self.grid.pt(cornerRadiusPx)\n\n                    let trackPath = NSBezierPath(roundedRect: rect, xRadius: radius, yRadius: radius)\n                    if drawTrackFill {\n                        baseFill.withAlphaComponent(trackFillAlpha * alpha).setFill()\n                        trackPath.fill()\n                    }\n\n                    // Crisp outline: stroke an inset path so the stroke stays within pixel bounds.\n                    let strokeWidthPx = 2 // 1 pt == 2 px at 2×\n                    let insetPx = strokeWidthPx / 2\n                    let strokeRect = Self.grid.rect(\n                        x: rectPx.x + insetPx,\n                        y: rectPx.y + insetPx,\n                        w: max(0, rectPx.w - insetPx * 2),\n                        h: max(0, rectPx.h - insetPx * 2))\n                    let strokePath = NSBezierPath(\n                        roundedRect: strokeRect,\n                        xRadius: Self.grid.pt(max(0, cornerRadiusPx - insetPx)),\n                        yRadius: Self.grid.pt(max(0, cornerRadiusPx - insetPx)))\n                    strokePath.lineWidth = CGFloat(strokeWidthPx) / Self.outputScale\n                    baseFill.withAlphaComponent(trackStrokeAlpha * alpha).setStroke()\n                    strokePath.stroke()\n\n                    // Fill: clip to the capsule and paint a left-to-right rect so the progress edge is straight.\n                    if let remaining {\n                        let clamped = max(0, min(remaining / 100, 1))\n                        let fillWidthPx = max(0, min(rectPx.w, Int((CGFloat(rectPx.w) * CGFloat(clamped)).rounded())))\n                        if fillWidthPx > 0 {\n                            NSGraphicsContext.current?.cgContext.saveGState()\n                            trackPath.addClip()\n                            fillColor.withAlphaComponent(alpha).setFill()\n                            NSBezierPath(\n                                rect: Self.grid.rect(\n                                    x: rectPx.x,\n                                    y: rectPx.y,\n                                    w: fillWidthPx,\n                                    h: rectPx.h)).fill()\n                            NSGraphicsContext.current?.cgContext.restoreGState()\n                        }\n                    }\n\n                    // Codex face: eye cutouts plus faint eyelids to give the prompt some personality.\n                    if addFace {\n                        let ctx = NSGraphicsContext.current?.cgContext\n                        let eyeSizePx = 4\n                        let eyeOffsetPx = 7\n                        let eyeCenterYPx = rectPx.y + rectPx.h / 2\n                        let centerXPx = rectPx.midXPx\n\n                        ctx?.saveGState()\n                        ctx?.setShouldAntialias(false)\n                        ctx?.clear(Self.grid.rect(\n                            x: centerXPx - eyeOffsetPx - eyeSizePx / 2,\n                            y: eyeCenterYPx - eyeSizePx / 2,\n                            w: eyeSizePx,\n                            h: eyeSizePx))\n                        ctx?.clear(Self.grid.rect(\n                            x: centerXPx + eyeOffsetPx - eyeSizePx / 2,\n                            y: eyeCenterYPx - eyeSizePx / 2,\n                            w: eyeSizePx,\n                            h: eyeSizePx))\n                        ctx?.restoreGState()\n\n                        // Blink: refill eyes from the top down using the bar fill color.\n                        if blink > 0.001 {\n                            let clamped = max(0, min(blink, 1))\n                            let blinkHeightPx = Int((CGFloat(eyeSizePx) * clamped).rounded())\n                            fillColor.withAlphaComponent(alpha).setFill()\n                            let blinkRectLeft = Self.grid.rect(\n                                x: centerXPx - eyeOffsetPx - eyeSizePx / 2,\n                                y: eyeCenterYPx + eyeSizePx / 2 - blinkHeightPx,\n                                w: eyeSizePx,\n                                h: blinkHeightPx)\n                            let blinkRectRight = Self.grid.rect(\n                                x: centerXPx + eyeOffsetPx - eyeSizePx / 2,\n                                y: eyeCenterYPx + eyeSizePx / 2 - blinkHeightPx,\n                                w: eyeSizePx,\n                                h: blinkHeightPx)\n                            NSBezierPath(rect: blinkRectLeft).fill()\n                            NSBezierPath(rect: blinkRectRight).fill()\n                        }\n\n                        // Hat: a tiny cap hovering above the eyes to give the face more character.\n                        let hatWidthPx = 18\n                        let hatHeightPx = 4\n                        let hatRect = Self.grid.rect(\n                            x: centerXPx - hatWidthPx / 2,\n                            y: rectPx.y + rectPx.h - hatHeightPx,\n                            w: hatWidthPx,\n                            h: hatHeightPx)\n                        ctx?.saveGState()\n                        if abs(tilt) > 0.0001 {\n                            // Tilt only the hat; keep eyes pixel-crisp and axis-aligned.\n                            let faceCenter = CGPoint(x: Self.grid.pt(centerXPx), y: Self.grid.pt(eyeCenterYPx))\n                            ctx?.translateBy(x: faceCenter.x, y: faceCenter.y)\n                            ctx?.rotate(by: tilt)\n                            ctx?.translateBy(x: -faceCenter.x, y: -faceCenter.y - abs(tilt) * 1.2)\n                        }\n                        fillColor.withAlphaComponent(alpha).setFill()\n                        NSBezierPath(rect: hatRect).fill()\n                        ctx?.restoreGState()\n                    }\n\n                    // Claude twist: blocky crab-style critter (arms + legs + vertical eyes).\n                    if addNotches {\n                        let ctx = NSGraphicsContext.current?.cgContext\n                        let wiggleOffset = Self.grid.snapDelta(wiggle * 0.6)\n                        let wigglePx = Int((wiggleOffset * Self.outputScale).rounded())\n\n                        fillColor.withAlphaComponent(alpha).setFill()\n\n                        // Arms/claws: mid-height side protrusions.\n                        // Keep within the 18×18pt canvas: barX is 3px, so 3px arms reach the edge without clipping.\n                        let armWidthPx = 3\n                        let armHeightPx = max(0, rectPx.h - 6)\n                        let armYPx = rectPx.y + 3 + wigglePx / 6\n                        let leftArm = Self.grid.rect(\n                            x: rectPx.x - armWidthPx,\n                            y: armYPx,\n                            w: armWidthPx,\n                            h: armHeightPx)\n                        let rightArm = Self.grid.rect(\n                            x: rectPx.x + rectPx.w,\n                            y: armYPx,\n                            w: armWidthPx,\n                            h: armHeightPx)\n                        NSBezierPath(rect: leftArm).fill()\n                        NSBezierPath(rect: rightArm).fill()\n\n                        // Legs: 4 little pixels underneath, like a tiny crab.\n                        let legCount = 4\n                        let legWidthPx = 2\n                        let legHeightPx = 3\n                        let legYPx = rectPx.y - legHeightPx + wigglePx / 6\n                        let stepPx = max(1, rectPx.w / (legCount + 1))\n                        for idx in 0..<legCount {\n                            let cx = rectPx.x + stepPx * (idx + 1)\n                            let leg = Self.grid.rect(\n                                x: cx - legWidthPx / 2,\n                                y: legYPx,\n                                w: legWidthPx,\n                                h: legHeightPx)\n                            NSBezierPath(rect: leg).fill()\n                        }\n\n                        // Eyes: tall vertical cutouts near the top.\n                        let eyeWidthPx = 2\n                        let eyeHeightPx = 5\n                        let eyeOffsetPx = 6\n                        let eyeYPx = rectPx.y + rectPx.h - eyeHeightPx - 2 + wigglePx / 8\n                        ctx?.saveGState()\n                        ctx?.setShouldAntialias(false)\n                        ctx?.clear(Self.grid.rect(\n                            x: rectPx.midXPx - eyeOffsetPx - eyeWidthPx / 2,\n                            y: eyeYPx,\n                            w: eyeWidthPx,\n                            h: eyeHeightPx))\n                        ctx?.clear(Self.grid.rect(\n                            x: rectPx.midXPx + eyeOffsetPx - eyeWidthPx / 2,\n                            y: eyeYPx,\n                            w: eyeWidthPx,\n                            h: eyeHeightPx))\n                        ctx?.restoreGState()\n\n                        // Blink: fill the eyes from the top down (blocky).\n                        if blink > 0.001 {\n                            let clamped = max(0, min(blink, 1))\n                            let blinkHeightPx = Int((CGFloat(eyeHeightPx) * clamped).rounded())\n                            fillColor.withAlphaComponent(alpha).setFill()\n                            let leftBlink = Self.grid.rect(\n                                x: rectPx.midXPx - eyeOffsetPx - eyeWidthPx / 2,\n                                y: eyeYPx + eyeHeightPx - blinkHeightPx,\n                                w: eyeWidthPx,\n                                h: blinkHeightPx)\n                            let rightBlink = Self.grid.rect(\n                                x: rectPx.midXPx + eyeOffsetPx - eyeWidthPx / 2,\n                                y: eyeYPx + eyeHeightPx - blinkHeightPx,\n                                w: eyeWidthPx,\n                                h: blinkHeightPx)\n                            NSBezierPath(rect: leftBlink).fill()\n                            NSBezierPath(rect: rightBlink).fill()\n                        }\n                    }\n\n                    // Gemini twist: sparkle-inspired design with prominent 4-pointed stars as eyes\n                    // and decorative points extending from the bar.\n                    if addGeminiTwist {\n                        let ctx = NSGraphicsContext.current?.cgContext\n                        let centerXPx = rectPx.midXPx\n                        let eyeCenterYPx = rectPx.y + rectPx.h / 2\n\n                        ctx?.saveGState()\n                        ctx?.setShouldAntialias(true)\n\n                        // 4-pointed star cutouts (Gemini sparkle eyes) - BIGGER\n                        let starSizePx = 8\n                        let eyeOffsetPx = 8\n                        let sr = Self.grid.pt(starSizePx / 2)\n                        let innerR = sr * 0.25\n\n                        func drawStarCutout(cx: CGFloat, cy: CGFloat) {\n                            let path = NSBezierPath()\n                            for i in 0..<8 {\n                                let angle = CGFloat(i) * .pi / 4 - .pi / 2\n                                let radius = (i % 2 == 0) ? sr : innerR\n                                let px = cx + cos(angle) * radius\n                                let py = cy + sin(angle) * radius\n                                if i == 0 {\n                                    path.move(to: NSPoint(x: px, y: py))\n                                } else {\n                                    path.line(to: NSPoint(x: px, y: py))\n                                }\n                            }\n                            path.close()\n                            path.fill()\n                        }\n\n                        let ldCx = Self.grid.pt(centerXPx - eyeOffsetPx)\n                        let rdCx = Self.grid.pt(centerXPx + eyeOffsetPx)\n                        let yCy = Self.grid.pt(eyeCenterYPx)\n\n                        // Clear star shapes for eyes\n                        ctx?.setBlendMode(.clear)\n                        drawStarCutout(cx: ldCx, cy: yCy)\n                        drawStarCutout(cx: rdCx, cy: yCy)\n                        ctx?.setBlendMode(.normal)\n\n                        // Decorative sparkle points extending from bar (sized to stay within 36px canvas)\n                        fillColor.withAlphaComponent(alpha).setFill()\n                        let pointHeightPx = 4\n                        let pointWidthPx = 4\n\n                        // Top center point (like a crown/sparkle)\n                        let topPointPath = NSBezierPath()\n                        let topCx = Self.grid.pt(centerXPx)\n                        let topBaseY = Self.grid.pt(rectPx.y + rectPx.h)\n                        let topPeakY = Self.grid.pt(rectPx.y + rectPx.h + pointHeightPx)\n                        let halfW = Self.grid.pt(pointWidthPx / 2)\n                        topPointPath.move(to: NSPoint(x: topCx - halfW, y: topBaseY))\n                        topPointPath.line(to: NSPoint(x: topCx, y: topPeakY))\n                        topPointPath.line(to: NSPoint(x: topCx + halfW, y: topBaseY))\n                        topPointPath.close()\n                        topPointPath.fill()\n\n                        // Bottom center point\n                        let bottomPointPath = NSBezierPath()\n                        let bottomBaseY = Self.grid.pt(rectPx.y)\n                        let bottomPeakY = Self.grid.pt(rectPx.y - pointHeightPx)\n                        bottomPointPath.move(to: NSPoint(x: topCx - halfW, y: bottomBaseY))\n                        bottomPointPath.line(to: NSPoint(x: topCx, y: bottomPeakY))\n                        bottomPointPath.line(to: NSPoint(x: topCx + halfW, y: bottomBaseY))\n                        bottomPointPath.close()\n                        bottomPointPath.fill()\n\n                        // Side points (max 3px to stay within canvas edge)\n                        let sidePointH = 3\n                        let sidePointW = 3\n                        let sideHalfW = Self.grid.pt(sidePointW / 2)\n                        let barMidY = Self.grid.pt(eyeCenterYPx)\n\n                        // Left side point\n                        let leftSidePath = NSBezierPath()\n                        let leftBaseX = Self.grid.pt(rectPx.x)\n                        let leftPeakX = Self.grid.pt(rectPx.x - sidePointH)\n                        leftSidePath.move(to: NSPoint(x: leftBaseX, y: barMidY - sideHalfW))\n                        leftSidePath.line(to: NSPoint(x: leftPeakX, y: barMidY))\n                        leftSidePath.line(to: NSPoint(x: leftBaseX, y: barMidY + sideHalfW))\n                        leftSidePath.close()\n                        leftSidePath.fill()\n\n                        // Right side point\n                        let rightSidePath = NSBezierPath()\n                        let rightBaseX = Self.grid.pt(rectPx.x + rectPx.w)\n                        let rightPeakX = Self.grid.pt(rectPx.x + rectPx.w + sidePointH)\n                        rightSidePath.move(to: NSPoint(x: rightBaseX, y: barMidY - sideHalfW))\n                        rightSidePath.line(to: NSPoint(x: rightPeakX, y: barMidY))\n                        rightSidePath.line(to: NSPoint(x: rightBaseX, y: barMidY + sideHalfW))\n                        rightSidePath.close()\n                        rightSidePath.fill()\n\n                        ctx?.restoreGState()\n\n                        // Blink: fill star eyes\n                        if blink > 0.001 {\n                            let clamped = max(0, min(blink, 1))\n                            fillColor.withAlphaComponent(alpha).setFill()\n                            let blinkR = sr * clamped\n                            let blinkInnerR = blinkR * 0.25\n\n                            func drawBlinkStar(cx: CGFloat, cy: CGFloat) {\n                                let path = NSBezierPath()\n                                for i in 0..<8 {\n                                    let angle = CGFloat(i) * .pi / 4 - .pi / 2\n                                    let radius = (i % 2 == 0) ? blinkR : blinkInnerR\n                                    let px = cx + cos(angle) * radius\n                                    let py = cy + sin(angle) * radius\n                                    if i == 0 {\n                                        path.move(to: NSPoint(x: px, y: py))\n                                    } else {\n                                        path.line(to: NSPoint(x: px, y: py))\n                                    }\n                                }\n                                path.close()\n                                path.fill()\n                            }\n\n                            drawBlinkStar(cx: ldCx, cy: yCy)\n                            drawBlinkStar(cx: rdCx, cy: yCy)\n                        }\n                    }\n\n                    if addAntigravityTwist {\n                        let dotSizePx = 3\n                        let dotOffsetXPx = rectPx.x + rectPx.w + 2\n                        let dotOffsetYPx = rectPx.y + rectPx.h - 2\n                        fillColor.withAlphaComponent(alpha).setFill()\n                        let dotRect = Self.grid.rect(\n                            x: dotOffsetXPx - dotSizePx / 2,\n                            y: dotOffsetYPx - dotSizePx / 2,\n                            w: dotSizePx,\n                            h: dotSizePx)\n                        NSBezierPath(ovalIn: dotRect).fill()\n                    }\n\n                    // Factory twist: 8-pointed asterisk/gear-like eyes with cog teeth accents\n                    if addFactoryTwist {\n                        let ctx = NSGraphicsContext.current?.cgContext\n                        let centerXPx = rectPx.midXPx\n                        let eyeCenterYPx = rectPx.y + rectPx.h / 2\n\n                        ctx?.saveGState()\n                        ctx?.setShouldAntialias(true)\n\n                        // 8-pointed asterisk cutouts (Factory gear-like eyes)\n                        let starSizePx = 7\n                        let eyeOffsetPx = 8\n                        let sr = Self.grid.pt(starSizePx / 2)\n                        let innerR = sr * 0.3\n\n                        func drawAsteriskCutout(cx: CGFloat, cy: CGFloat) {\n                            let path = NSBezierPath()\n                            // 8 points for the asterisk\n                            for i in 0..<16 {\n                                let angle = CGFloat(i) * .pi / 8 - .pi / 2\n                                let radius = (i % 2 == 0) ? sr : innerR\n                                let px = cx + cos(angle) * radius\n                                let py = cy + sin(angle) * radius\n                                if i == 0 {\n                                    path.move(to: NSPoint(x: px, y: py))\n                                } else {\n                                    path.line(to: NSPoint(x: px, y: py))\n                                }\n                            }\n                            path.close()\n                            path.fill()\n                        }\n\n                        let ldCx = Self.grid.pt(centerXPx - eyeOffsetPx)\n                        let rdCx = Self.grid.pt(centerXPx + eyeOffsetPx)\n                        let yCy = Self.grid.pt(eyeCenterYPx)\n\n                        // Clear asterisk shapes for eyes\n                        ctx?.setBlendMode(.clear)\n                        drawAsteriskCutout(cx: ldCx, cy: yCy)\n                        drawAsteriskCutout(cx: rdCx, cy: yCy)\n                        ctx?.setBlendMode(.normal)\n\n                        // Small gear teeth on top and bottom edges\n                        fillColor.withAlphaComponent(alpha).setFill()\n                        let toothWidthPx = 3\n                        let toothHeightPx = 2\n\n                        // Top teeth (2 small rectangles)\n                        let topY = Self.grid.pt(rectPx.y + rectPx.h)\n                        let tooth1X = Self.grid.pt(centerXPx - 5 - toothWidthPx / 2)\n                        let tooth2X = Self.grid.pt(centerXPx + 5 - toothWidthPx / 2)\n                        NSBezierPath(rect: CGRect(\n                            x: tooth1X,\n                            y: topY,\n                            width: Self.grid.pt(toothWidthPx),\n                            height: Self.grid.pt(toothHeightPx))).fill()\n                        NSBezierPath(rect: CGRect(\n                            x: tooth2X,\n                            y: topY,\n                            width: Self.grid.pt(toothWidthPx),\n                            height: Self.grid.pt(toothHeightPx))).fill()\n\n                        // Bottom teeth\n                        let bottomY = Self.grid.pt(rectPx.y - toothHeightPx)\n                        NSBezierPath(rect: CGRect(\n                            x: tooth1X,\n                            y: bottomY,\n                            width: Self.grid.pt(toothWidthPx),\n                            height: Self.grid.pt(toothHeightPx))).fill()\n                        NSBezierPath(rect: CGRect(\n                            x: tooth2X,\n                            y: bottomY,\n                            width: Self.grid.pt(toothWidthPx),\n                            height: Self.grid.pt(toothHeightPx))).fill()\n\n                        ctx?.restoreGState()\n\n                        // Blink: fill asterisk eyes\n                        if blink > 0.001 {\n                            let clamped = max(0, min(blink, 1))\n                            fillColor.withAlphaComponent(alpha).setFill()\n                            let blinkR = sr * clamped\n                            let blinkInnerR = blinkR * 0.3\n\n                            func drawBlinkAsterisk(cx: CGFloat, cy: CGFloat) {\n                                let path = NSBezierPath()\n                                for i in 0..<16 {\n                                    let angle = CGFloat(i) * .pi / 8 - .pi / 2\n                                    let radius = (i % 2 == 0) ? blinkR : blinkInnerR\n                                    let px = cx + cos(angle) * radius\n                                    let py = cy + sin(angle) * radius\n                                    if i == 0 {\n                                        path.move(to: NSPoint(x: px, y: py))\n                                    } else {\n                                        path.line(to: NSPoint(x: px, y: py))\n                                    }\n                                }\n                                path.close()\n                                path.fill()\n                            }\n\n                            drawBlinkAsterisk(cx: ldCx, cy: yCy)\n                            drawBlinkAsterisk(cx: rdCx, cy: yCy)\n                        }\n                    }\n\n                    // Warp twist: \"Warp\" style face with tilted-eye cutouts.\n                    if addWarpTwist {\n                        let ctx = NSGraphicsContext.current?.cgContext\n                        let centerXPx = rectPx.midXPx\n                        let eyeCenterYPx = rectPx.y + rectPx.h / 2\n\n                        ctx?.saveGState()\n                        ctx?.setShouldAntialias(true) // Smooth edges for tilted ellipse eyes\n\n                        // 1. Draw Eyes (Tilted ellipse cutouts - \"fox eye\" / \"cat eye\" style)\n                        // Keep sizes in integer pixels so grid conversion stays exact.\n                        let eyeWidthPx = 5\n                        let eyeHeightPx = 8\n                        let eyeOffsetPx = 7\n                        let eyeTiltAngle: CGFloat = .pi / 3 // 60 degrees tilt\n\n                        let leftEyeCx = Self.grid.pt(centerXPx) - Self.grid.pt(eyeOffsetPx)\n                        let rightEyeCx = Self.grid.pt(centerXPx) + Self.grid.pt(eyeOffsetPx)\n                        let eyeCy = Self.grid.pt(eyeCenterYPx)\n                        let eyeW = Self.grid.pt(eyeWidthPx)\n                        let eyeH = Self.grid.pt(eyeHeightPx)\n\n                        /// Draw a tilted ellipse eye at the given center.\n                        func drawTiltedEyeCutout(cx: CGFloat, cy: CGFloat, tiltAngle: CGFloat) {\n                            guard let ctx else { return }\n                            let eyeRect = CGRect(x: -eyeW / 2, y: -eyeH / 2, width: eyeW, height: eyeH)\n\n                            // Use CGContext transforms instead of AffineTransform-on-path so the rotation origin\n                            // is unambiguous and the current blend mode is consistently respected.\n                            ctx.saveGState()\n                            ctx.translateBy(x: cx, y: cy)\n                            ctx.rotate(by: tiltAngle)\n                            ctx.addEllipse(in: eyeRect)\n                            ctx.fillPath()\n                            ctx.restoreGState()\n                        }\n\n                        if warpEyesFilled {\n                            fillColor.withAlphaComponent(alpha).setFill()\n                            drawTiltedEyeCutout(cx: leftEyeCx, cy: eyeCy, tiltAngle: eyeTiltAngle)\n                            drawTiltedEyeCutout(cx: rightEyeCx, cy: eyeCy, tiltAngle: -eyeTiltAngle)\n                        } else {\n                            // Clear eyes using blend mode\n                            ctx?.setBlendMode(.clear)\n                            drawTiltedEyeCutout(cx: leftEyeCx, cy: eyeCy, tiltAngle: eyeTiltAngle)\n                            drawTiltedEyeCutout(cx: rightEyeCx, cy: eyeCy, tiltAngle: -eyeTiltAngle)\n                            ctx?.setBlendMode(.normal)\n                        }\n                        ctx?.restoreGState() // Restore graphics state\n                    }\n                }\n\n                let effectiveWeeklyRemaining: Double? = {\n                    if style == .warp, let weeklyRemaining, weeklyRemaining <= 0 {\n                        return nil\n                    }\n                    return weeklyRemaining\n                }()\n                let topValue = primaryRemaining\n                let bottomValue = effectiveWeeklyRemaining\n                let creditsRatio = creditsRemaining.map { min($0 / Self.creditsCap * 100, 100) }\n\n                let hasWeekly = (bottomValue != nil)\n                let weeklyAvailable = hasWeekly && (bottomValue ?? 0) > 0\n                let creditsAlpha: CGFloat = 1.0\n                let topRectPx = RectPx(x: barXPx, y: 19, w: barWidthPx, h: 12)\n                let bottomRectPx = RectPx(x: barXPx, y: 5, w: barWidthPx, h: 8)\n                let creditsRectPx = RectPx(x: barXPx, y: 14, w: barWidthPx, h: 16)\n                let creditsBottomRectPx = RectPx(x: barXPx, y: 4, w: barWidthPx, h: 6)\n\n                // Warp special case: when no bonus or bonus exhausted, show \"top monthly, bottom dimmed\"\n                let warpNoBonus = style == .warp && !weeklyAvailable\n\n                if weeklyAvailable {\n                    // Normal: top=primary, bottom=secondary (bonus/weekly).\n                    drawBar(\n                        rectPx: topRectPx,\n                        remaining: topValue,\n                        addNotches: style == .claude,\n                        addFace: style == .codex,\n                        addGeminiTwist: style == .gemini || style == .antigravity,\n                        addAntigravityTwist: style == .antigravity,\n                        addFactoryTwist: style == .factory,\n                        addWarpTwist: style == .warp,\n                        blink: blink)\n                    drawBar(rectPx: bottomRectPx, remaining: bottomValue)\n                } else if !hasWeekly || warpNoBonus {\n                    if style == .warp {\n                        // Warp: no bonus or bonus exhausted -> top=monthly credits, bottom=dimmed track\n                        drawBar(\n                            rectPx: topRectPx,\n                            remaining: topValue,\n                            addWarpTwist: true,\n                            blink: blink)\n                        drawBar(rectPx: bottomRectPx, remaining: nil, alpha: 0.45)\n                    } else {\n                        // Weekly missing (e.g. Claude enterprise): keep normal layout but\n                        // dim the bottom track to indicate N/A.\n                        if topValue == nil, let ratio = creditsRatio {\n                            // Credits-only: show credits prominently (e.g. credits loaded before usage).\n                            drawBar(\n                                rectPx: creditsRectPx,\n                                remaining: ratio,\n                                alpha: creditsAlpha,\n                                addNotches: style == .claude,\n                                addFace: style == .codex,\n                                addGeminiTwist: style == .gemini || style == .antigravity,\n                                addAntigravityTwist: style == .antigravity,\n                                addFactoryTwist: style == .factory,\n                                addWarpTwist: style == .warp,\n                                blink: blink)\n                            drawBar(rectPx: creditsBottomRectPx, remaining: nil, alpha: 0.45)\n                        } else {\n                            drawBar(\n                                rectPx: topRectPx,\n                                remaining: topValue,\n                                addNotches: style == .claude,\n                                addFace: style == .codex,\n                                addGeminiTwist: style == .gemini || style == .antigravity,\n                                addAntigravityTwist: style == .antigravity,\n                                addFactoryTwist: style == .factory,\n                                addWarpTwist: style == .warp,\n                                blink: blink)\n                            drawBar(rectPx: bottomRectPx, remaining: nil, alpha: 0.45)\n                        }\n                    }\n                } else {\n                    // Weekly exhausted/missing: show credits on top (thicker), weekly (likely 0) on bottom.\n                    if let ratio = creditsRatio {\n                        drawBar(\n                            rectPx: creditsRectPx,\n                            remaining: ratio,\n                            alpha: creditsAlpha,\n                            addNotches: style == .claude,\n                            addFace: style == .codex,\n                            addGeminiTwist: style == .gemini || style == .antigravity,\n                            addAntigravityTwist: style == .antigravity,\n                            addFactoryTwist: style == .factory,\n                            addWarpTwist: style == .warp,\n                            blink: blink)\n                    } else {\n                        // No credits available; fall back to 5h if present.\n                        drawBar(\n                            rectPx: topRectPx,\n                            remaining: topValue,\n                            addNotches: style == .claude,\n                            addFace: style == .codex,\n                            addGeminiTwist: style == .gemini || style == .antigravity,\n                            addAntigravityTwist: style == .antigravity,\n                            addFactoryTwist: style == .factory,\n                            addWarpTwist: style == .warp,\n                            blink: blink)\n                    }\n                    drawBar(rectPx: creditsBottomRectPx, remaining: bottomValue)\n                }\n\n                Self.drawStatusOverlay(indicator: statusIndicator)\n            }\n        }\n\n        if shouldCache {\n            let key = IconCacheKey(\n                primary: self.quantizedPercent(primaryRemaining),\n                weekly: self.quantizedPercent(weeklyRemaining),\n                credits: self.quantizedCredits(creditsRemaining),\n                stale: stale,\n                style: self.styleKey(style),\n                indicator: self.indicatorKey(statusIndicator))\n            if let cached = self.cachedIcon(for: key) {\n                return cached\n            }\n            let image = render()\n            self.storeIcon(image, for: key)\n            return image\n        }\n\n        return render()\n    }\n\n    // swiftlint:enable function_body_length\n\n    /// Morph helper: unbraids a simplified knot into our bar icon.\n    static func makeMorphIcon(progress: Double, style: IconStyle) -> NSImage {\n        let clamped = max(0, min(progress, 1))\n        let key = self.morphCacheKey(progress: clamped, style: style)\n        if let cached = self.morphCache.image(for: key) {\n            return cached\n        }\n        let image = self.renderImage {\n            self.drawUnbraidMorph(t: clamped, style: style)\n        }\n        self.morphCache.set(image, for: key)\n        return image\n    }\n\n    private static func quantizedPercent(_ value: Double?) -> Int {\n        guard let value else { return -1 }\n        return Int((value * 10).rounded())\n    }\n\n    private static func quantizedCredits(_ value: Double?) -> Int {\n        guard let value else { return -1 }\n        let clamped = max(0, min(value, self.creditsCap))\n        return Int((clamped * 10).rounded())\n    }\n\n    private static let styleKeyLookup: [IconStyle: Int] = {\n        var lookup: [IconStyle: Int] = [:]\n        for (index, style) in IconStyle.allCases.enumerated() {\n            lookup[style] = index\n        }\n        return lookup\n    }()\n\n    private static func styleKey(_ style: IconStyle) -> Int {\n        self.styleKeyLookup[style] ?? 0\n    }\n\n    private static func indicatorKey(_ indicator: ProviderStatusIndicator) -> Int {\n        switch indicator {\n        case .none: 0\n        case .minor: 1\n        case .major: 2\n        case .critical: 3\n        case .maintenance: 4\n        case .unknown: 5\n        }\n    }\n\n    private static func morphCacheKey(progress: Double, style: IconStyle) -> NSNumber {\n        let bucket = Int((progress * Double(self.morphBucketCount)).rounded())\n        let key = self.styleKey(style) * 1000 + bucket\n        return NSNumber(value: key)\n    }\n\n    private static func cachedIcon(for key: IconCacheKey) -> NSImage? {\n        self.iconCacheStore.cachedIcon(for: key)\n    }\n\n    private static func storeIcon(_ image: NSImage, for key: IconCacheKey) {\n        self.iconCacheStore.storeIcon(image, for: key, limit: self.iconCacheLimit)\n    }\n\n    private static func drawUnbraidMorph(t: Double, style: IconStyle) {\n        let t = CGFloat(max(0, min(t, 1)))\n        let size = Self.baseSize\n        let center = CGPoint(x: size.width / 2, y: size.height / 2)\n        let baseColor = NSColor.labelColor\n\n        struct Segment {\n            let startCenter: CGPoint\n            let endCenter: CGPoint\n            let startAngle: CGFloat\n            let endAngle: CGFloat\n            let startLength: CGFloat\n            let endLength: CGFloat\n            let startThickness: CGFloat\n            let endThickness: CGFloat\n            let fadeOut: Bool\n        }\n\n        let segments: [Segment] = [\n            // Upper ribbon -> top bar\n            .init(\n                startCenter: center.offset(dx: 0, dy: 2),\n                endCenter: CGPoint(x: center.x, y: 9.0),\n                startAngle: -30,\n                endAngle: 0,\n                startLength: 16,\n                endLength: 14,\n                startThickness: 3.4,\n                endThickness: 3.0,\n                fadeOut: false),\n            // Lower ribbon -> bottom bar\n            .init(\n                startCenter: center.offset(dx: 0, dy: -2),\n                endCenter: CGPoint(x: center.x, y: 4.0),\n                startAngle: 210,\n                endAngle: 0,\n                startLength: 16,\n                endLength: 12,\n                startThickness: 3.4,\n                endThickness: 2.4,\n                fadeOut: false),\n            // Side ribbon fades away\n            .init(\n                startCenter: center,\n                endCenter: center.offset(dx: 0, dy: 6),\n                startAngle: 90,\n                endAngle: 0,\n                startLength: 16,\n                endLength: 8,\n                startThickness: 3.4,\n                endThickness: 1.8,\n                fadeOut: true),\n        ]\n\n        for seg in segments {\n            let p = seg.fadeOut ? t * 1.1 : t\n            let c = seg.startCenter.lerp(to: seg.endCenter, p: p)\n            let angle = seg.startAngle.lerp(to: seg.endAngle, p: p)\n            let length = seg.startLength.lerp(to: seg.endLength, p: p)\n            let thickness = seg.startThickness.lerp(to: seg.endThickness, p: p)\n            let alpha = seg.fadeOut ? (1 - p) : 1\n\n            self.drawRoundedRibbon(\n                center: c,\n                length: length,\n                thickness: thickness,\n                angle: angle,\n                color: baseColor.withAlphaComponent(alpha))\n        }\n\n        // Cross-fade in bar fill emphasis near the end of the morph.\n        if t > 0.55 {\n            let barT = (t - 0.55) / 0.45\n            let bars = self.makeIcon(\n                primaryRemaining: 100,\n                weeklyRemaining: 100,\n                creditsRemaining: nil,\n                stale: false,\n                style: style)\n            bars.draw(in: CGRect(origin: .zero, size: size), from: .zero, operation: .sourceOver, fraction: barT)\n        }\n    }\n\n    private static func drawRoundedRibbon(\n        center: CGPoint,\n        length: CGFloat,\n        thickness: CGFloat,\n        angle: CGFloat,\n        color: NSColor)\n    {\n        var transform = AffineTransform.identity\n        transform.translate(x: center.x, y: center.y)\n        transform.rotate(byDegrees: angle)\n        transform.translate(x: -center.x, y: -center.y)\n\n        let rect = CGRect(\n            x: center.x - length / 2,\n            y: center.y - thickness / 2,\n            width: length,\n            height: thickness)\n\n        let path = NSBezierPath(roundedRect: rect, xRadius: thickness / 2, yRadius: thickness / 2)\n        path.transform(using: transform)\n        color.setFill()\n        path.fill()\n    }\n\n    private static func drawStatusOverlay(indicator: ProviderStatusIndicator) {\n        guard indicator.hasIssue else { return }\n        let color = NSColor.labelColor\n\n        switch indicator {\n        case .minor, .maintenance:\n            let size: CGFloat = 4\n            let rect = Self.snapRect(\n                x: Self.baseSize.width - size - 2,\n                y: 2,\n                width: size,\n                height: size)\n            let path = NSBezierPath(ovalIn: rect)\n            color.setFill()\n            path.fill()\n        case .major, .critical, .unknown:\n            let lineRect = Self.snapRect(\n                x: Self.baseSize.width - 6,\n                y: 4,\n                width: 2.0,\n                height: 6)\n            let linePath = NSBezierPath(roundedRect: lineRect, xRadius: 1, yRadius: 1)\n            color.setFill()\n            linePath.fill()\n\n            let dotRect = Self.snapRect(\n                x: Self.baseSize.width - 6,\n                y: 2,\n                width: 2.0,\n                height: 2.0)\n            NSBezierPath(ovalIn: dotRect).fill()\n        case .none:\n            break\n        }\n    }\n\n    private static func withScaledContext(_ draw: () -> Void) {\n        guard let ctx = NSGraphicsContext.current?.cgContext else {\n            draw()\n            return\n        }\n        ctx.saveGState()\n        ctx.setShouldAntialias(true)\n        ctx.interpolationQuality = .none\n        draw()\n        ctx.restoreGState()\n    }\n\n    private static func snap(_ value: CGFloat) -> CGFloat {\n        (value * self.outputScale).rounded() / self.outputScale\n    }\n\n    private static func snapRect(x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat) -> CGRect {\n        CGRect(x: self.snap(x), y: self.snap(y), width: self.snap(width), height: self.snap(height))\n    }\n\n    private static func renderImage(_ draw: () -> Void) -> NSImage {\n        let image = NSImage(size: Self.outputSize)\n\n        if let rep = NSBitmapImageRep(\n            bitmapDataPlanes: nil,\n            pixelsWide: Int(Self.outputSize.width * Self.outputScale),\n            pixelsHigh: Int(Self.outputSize.height * Self.outputScale),\n            bitsPerSample: 8,\n            samplesPerPixel: 4,\n            hasAlpha: true,\n            isPlanar: false,\n            colorSpaceName: .deviceRGB,\n            bytesPerRow: 0,\n            bitsPerPixel: 0)\n        {\n            rep.size = Self.outputSize // points\n            image.addRepresentation(rep)\n\n            NSGraphicsContext.saveGraphicsState()\n            if let ctx = NSGraphicsContext(bitmapImageRep: rep) {\n                NSGraphicsContext.current = ctx\n                Self.withScaledContext(draw)\n            }\n            NSGraphicsContext.restoreGraphicsState()\n        } else {\n            // Fallback to legacy focus if the bitmap rep fails for any reason.\n            image.lockFocus()\n            Self.withScaledContext(draw)\n            image.unlockFocus()\n        }\n\n        image.isTemplate = true\n        return image\n    }\n}\n\nextension CGPoint {\n    fileprivate func lerp(to other: CGPoint, p: CGFloat) -> CGPoint {\n        CGPoint(x: self.x + (other.x - self.x) * p, y: self.y + (other.y - self.y) * p)\n    }\n\n    fileprivate func offset(dx: CGFloat, dy: CGFloat) -> CGPoint {\n        CGPoint(x: self.x + dx, y: self.y + dy)\n    }\n}\n\nextension CGFloat {\n    fileprivate func lerp(to other: CGFloat, p: CGFloat) -> CGFloat {\n        self + (other - self) * p\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/IconView.swift",
    "content": "import CodexBarCore\nimport SwiftUI\n\n@MainActor\nstruct IconView: View {\n    let snapshot: UsageSnapshot?\n    let creditsRemaining: Double?\n    let isStale: Bool\n    let showLoadingAnimation: Bool\n    let style: IconStyle\n    @State private var phase: CGFloat = 0\n    @State private var displayLink = DisplayLinkDriver()\n    @State private var pattern: LoadingPattern = .knightRider\n    @State private var debugCycle = false\n    @State private var cycleIndex = 0\n    @State private var cycleCounter = 0\n    private let loadingFPS: Double = 12\n    // Advance to next pattern every N ticks when debug cycling.\n    private let cycleIntervalTicks = 20\n    private let patterns = LoadingPattern.allCases\n\n    private var isLoading: Bool {\n        self.showLoadingAnimation && self.snapshot == nil\n    }\n\n    var body: some View {\n        Group {\n            if let snapshot {\n                Image(nsImage: IconRenderer.makeIcon(\n                    primaryRemaining: snapshot.primary?.remainingPercent,\n                    weeklyRemaining: snapshot.secondary?.remainingPercent,\n                    creditsRemaining: self.creditsRemaining,\n                    stale: self.isStale,\n                    style: self.style))\n                    .renderingMode(.original)\n                    .interpolation(.none)\n                    .frame(width: 20, height: 18, alignment: .center)\n                    .padding(.horizontal, 2)\n            } else if self.showLoadingAnimation {\n                // Loading: animate bars with the current pattern until data arrives.\n                Image(nsImage: self.loadingImage)\n                    .renderingMode(.original)\n                    .interpolation(.none)\n                    .frame(width: 20, height: 18, alignment: .center)\n                    .padding(.horizontal, 2)\n                    .onChange(of: self.displayLink.tick) { _, _ in\n                        self.phase += 0.09 // half-speed animation\n                        if self.debugCycle {\n                            self.cycleCounter += 1\n                            if self.cycleCounter >= self.cycleIntervalTicks {\n                                self.cycleCounter = 0\n                                self.cycleIndex = (self.cycleIndex + 1) % self.patterns.count\n                                self.pattern = self.patterns[self.cycleIndex]\n                            }\n                        }\n                    }\n            } else {\n                // No animation when usage/account is unavailable; show empty tracks.\n                Image(nsImage: IconRenderer.makeIcon(\n                    primaryRemaining: nil,\n                    weeklyRemaining: nil,\n                    creditsRemaining: self.creditsRemaining,\n                    stale: self.isStale,\n                    style: self.style))\n                    .renderingMode(.original)\n                    .interpolation(.none)\n                    .frame(width: 20, height: 18, alignment: .center)\n                    .padding(.horizontal, 2)\n            }\n        }\n        .onChange(of: self.isLoading, initial: true) { _, isLoading in\n            if isLoading {\n                self.displayLink.start(fps: self.loadingFPS)\n                if !self.debugCycle {\n                    self.pattern = self.patterns.randomElement() ?? .knightRider\n                }\n            } else {\n                self.displayLink.stop()\n                self.debugCycle = false\n                self.phase = 0\n            }\n        }\n        .onDisappear { self.displayLink.stop() }\n        .onReceive(NotificationCenter.default.publisher(for: .codexbarDebugReplayAllAnimations)) { notification in\n            if let raw = notification.userInfo?[\"pattern\"] as? String,\n               let selected = LoadingPattern(rawValue: raw)\n            {\n                self.debugCycle = false\n                self.pattern = selected\n                self.cycleIndex = self.patterns.firstIndex(of: selected) ?? 0\n            } else {\n                self.debugCycle = true\n                self.cycleIndex = 0\n                self.pattern = self.patterns.first ?? .knightRider\n            }\n            self.cycleCounter = 0\n            self.phase = 0\n        }\n    }\n\n    private var loadingPrimary: Double {\n        self.pattern.value(phase: Double(self.phase))\n    }\n\n    private var loadingSecondary: Double {\n        self.pattern.value(phase: Double(self.phase + self.pattern.secondaryOffset))\n    }\n\n    private var loadingImage: NSImage {\n        if self.pattern == .unbraid {\n            let progress = self.loadingPrimary / 100\n            return IconRenderer.makeMorphIcon(progress: progress, style: self.style)\n        } else {\n            return IconRenderer.makeIcon(\n                primaryRemaining: self.loadingPrimary,\n                weeklyRemaining: self.loadingSecondary,\n                creditsRemaining: nil,\n                stale: false,\n                style: self.style)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/InstallOrigin.swift",
    "content": "import Foundation\n\nenum InstallOrigin {\n    static func isHomebrewCask(appBundleURL: URL) -> Bool {\n        let resolved = appBundleURL.resolvingSymlinksInPath()\n        let path = resolved.path\n        return path.contains(\"/Caskroom/\") || path.contains(\"/Homebrew/Caskroom/\")\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/KeyboardShortcuts+Names.swift",
    "content": "import KeyboardShortcuts\n\n@MainActor\nextension KeyboardShortcuts.Name {\n    static let openMenu = Self(\"openMenu\")\n}\n"
  },
  {
    "path": "Sources/CodexBar/KeychainMigration.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Security\n\n/// Migrates keychain items to use kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly\n/// to prevent permission prompts on every rebuild during development.\nenum KeychainMigration {\n    private static let log = CodexBarLog.logger(LogCategories.keychainMigration)\n    private static let migrationKey = \"KeychainMigrationV1Completed\"\n\n    struct MigrationItem: Hashable {\n        let service: String\n        let account: String?\n\n        var label: String {\n            let accountLabel = self.account ?? \"<any>\"\n            return \"\\(self.service):\\(accountLabel)\"\n        }\n    }\n\n    static let itemsToMigrate: [MigrationItem] = [\n        MigrationItem(service: \"com.steipete.CodexBar\", account: \"codex-cookie\"),\n        MigrationItem(service: \"com.steipete.CodexBar\", account: \"claude-cookie\"),\n        MigrationItem(service: \"com.steipete.CodexBar\", account: \"cursor-cookie\"),\n        MigrationItem(service: \"com.steipete.CodexBar\", account: \"factory-cookie\"),\n        MigrationItem(service: \"com.steipete.CodexBar\", account: \"minimax-cookie\"),\n        MigrationItem(service: \"com.steipete.CodexBar\", account: \"minimax-api-token\"),\n        MigrationItem(service: \"com.steipete.CodexBar\", account: \"augment-cookie\"),\n        MigrationItem(service: \"com.steipete.CodexBar\", account: \"copilot-api-token\"),\n        MigrationItem(service: \"com.steipete.CodexBar\", account: \"zai-api-token\"),\n        MigrationItem(service: \"com.steipete.CodexBar\", account: \"synthetic-api-key\"),\n    ]\n\n    /// Run migration once per installation\n    static func migrateIfNeeded() {\n        guard !KeychainAccessGate.isDisabled else {\n            self.log.info(\"Keychain access disabled; skipping migration\")\n            return\n        }\n\n        if !UserDefaults.standard.bool(forKey: self.migrationKey) {\n            self.log.info(\"Starting keychain migration to reduce permission prompts\")\n\n            var migratedCount = 0\n            var errorCount = 0\n\n            for item in self.itemsToMigrate {\n                do {\n                    if try self.migrateItem(item) {\n                        migratedCount += 1\n                    }\n                } catch {\n                    errorCount += 1\n                    self.log.error(\"Failed to migrate \\(item.label): \\(String(describing: error))\")\n                }\n            }\n\n            self.log.info(\"Keychain migration complete: \\(migratedCount) migrated, \\(errorCount) errors\")\n            UserDefaults.standard.set(true, forKey: self.migrationKey)\n\n            if migratedCount > 0 {\n                self.log.info(\"✅ Future rebuilds will not prompt for keychain access\")\n            }\n        } else {\n            self.log.debug(\"Keychain migration already completed, skipping\")\n        }\n    }\n\n    /// Migrate a single keychain item to the new accessibility level\n    /// Returns true if item was migrated, false if item didn't exist\n    private static func migrateItem(_ item: MigrationItem) throws -> Bool {\n        // First, try to read the existing item\n        var result: CFTypeRef?\n        var query: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: item.service,\n            kSecMatchLimit as String: kSecMatchLimitOne,\n            kSecReturnData as String: true,\n            kSecReturnAttributes as String: true,\n        ]\n        if let account = item.account {\n            query[kSecAttrAccount as String] = account\n        }\n\n        let status = SecItemCopyMatching(query as CFDictionary, &result)\n\n        if status == errSecItemNotFound {\n            // Item doesn't exist, nothing to migrate\n            return false\n        }\n\n        guard status == errSecSuccess else {\n            throw KeychainMigrationError.readFailed(status)\n        }\n\n        guard let rawItem = result as? [String: Any],\n              let data = rawItem[kSecValueData as String] as? Data,\n              let accessible = rawItem[kSecAttrAccessible as String] as? String\n        else {\n            throw KeychainMigrationError.invalidItemFormat\n        }\n\n        // Check if already using the correct accessibility\n        if accessible == (kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly as String) {\n            self.log.debug(\"\\(item.label) already using correct accessibility\")\n            return false\n        }\n\n        // Delete the old item\n        var deleteQuery: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: item.service,\n        ]\n        if let account = item.account {\n            deleteQuery[kSecAttrAccount as String] = account\n        }\n\n        let deleteStatus = SecItemDelete(deleteQuery as CFDictionary)\n        guard deleteStatus == errSecSuccess else {\n            throw KeychainMigrationError.deleteFailed(deleteStatus)\n        }\n\n        // Add it back with the new accessibility\n        var addQuery: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: item.service,\n            kSecValueData as String: data,\n            kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,\n        ]\n        if let account = item.account {\n            addQuery[kSecAttrAccount as String] = account\n        }\n\n        let addStatus = SecItemAdd(addQuery as CFDictionary, nil)\n        guard addStatus == errSecSuccess else {\n            throw KeychainMigrationError.addFailed(addStatus)\n        }\n\n        self.log.info(\"Migrated \\(item.label) to new accessibility level\")\n        return true\n    }\n\n    /// Reset migration flag (for testing)\n    static func resetMigrationFlag() {\n        UserDefaults.standard.removeObject(forKey: self.migrationKey)\n    }\n}\n\nenum KeychainMigrationError: Error {\n    case readFailed(OSStatus)\n    case deleteFailed(OSStatus)\n    case addFailed(OSStatus)\n    case invalidItemFormat\n}\n"
  },
  {
    "path": "Sources/CodexBar/KeychainPromptCoordinator.swift",
    "content": "import AppKit\nimport CodexBarCore\nimport SweetCookieKit\n\nenum KeychainPromptCoordinator {\n    private static let promptLock = NSLock()\n    private static let log = CodexBarLog.logger(LogCategories.keychainPrompt)\n\n    static func install() {\n        KeychainPromptHandler.handler = { context in\n            self.presentKeychainPrompt(context)\n        }\n        BrowserCookieKeychainPromptHandler.handler = { context in\n            self.presentBrowserCookiePrompt(context)\n        }\n    }\n\n    private static func presentKeychainPrompt(_ context: KeychainPromptContext) {\n        let (title, message) = self.keychainCopy(for: context)\n        self.log.info(\"Keychain prompt requested\", metadata: [\"kind\": \"\\(context.kind)\"])\n        self.presentAlert(title: title, message: message)\n    }\n\n    private static func presentBrowserCookiePrompt(_ context: BrowserCookieKeychainPromptContext) {\n        let title = \"Keychain Access Required\"\n        let message = [\n            \"CodexBar will ask macOS Keychain for “\\(context.label)” so it can decrypt browser cookies\",\n            \"and authenticate your account. Click OK to continue.\",\n        ].joined(separator: \" \")\n        self.log.info(\"Browser cookie keychain prompt requested\", metadata: [\"label\": context.label])\n        self.presentAlert(title: title, message: message)\n    }\n\n    private static func keychainCopy(for context: KeychainPromptContext) -> (title: String, message: String) {\n        let title = \"Keychain Access Required\"\n        switch context.kind {\n        case .claudeOAuth:\n            return (title, [\n                \"CodexBar will ask macOS Keychain for the Claude Code OAuth token\",\n                \"so it can fetch your Claude usage. Click OK to continue.\",\n            ].joined(separator: \" \"))\n        case .codexCookie:\n            return (title, [\n                \"CodexBar will ask macOS Keychain for your OpenAI cookie header\",\n                \"so it can fetch Codex dashboard extras. Click OK to continue.\",\n            ].joined(separator: \" \"))\n        case .claudeCookie:\n            return (title, [\n                \"CodexBar will ask macOS Keychain for your Claude cookie header\",\n                \"so it can fetch Claude web usage. Click OK to continue.\",\n            ].joined(separator: \" \"))\n        case .cursorCookie:\n            return (title, [\n                \"CodexBar will ask macOS Keychain for your Cursor cookie header\",\n                \"so it can fetch usage. Click OK to continue.\",\n            ].joined(separator: \" \"))\n        case .opencodeCookie:\n            return (title, [\n                \"CodexBar will ask macOS Keychain for your OpenCode cookie header\",\n                \"so it can fetch usage. Click OK to continue.\",\n            ].joined(separator: \" \"))\n        case .factoryCookie:\n            return (title, [\n                \"CodexBar will ask macOS Keychain for your Factory cookie header\",\n                \"so it can fetch usage. Click OK to continue.\",\n            ].joined(separator: \" \"))\n        case .zaiToken:\n            return (title, [\n                \"CodexBar will ask macOS Keychain for your z.ai API token\",\n                \"so it can fetch usage. Click OK to continue.\",\n            ].joined(separator: \" \"))\n        case .syntheticToken:\n            return (title, [\n                \"CodexBar will ask macOS Keychain for your Synthetic API key\",\n                \"so it can fetch usage. Click OK to continue.\",\n            ].joined(separator: \" \"))\n        case .copilotToken:\n            return (title, [\n                \"CodexBar will ask macOS Keychain for your GitHub Copilot token\",\n                \"so it can fetch usage. Click OK to continue.\",\n            ].joined(separator: \" \"))\n        case .kimiToken:\n            return (title, [\n                \"CodexBar will ask macOS Keychain for your Kimi auth token\",\n                \"so it can fetch usage. Click OK to continue.\",\n            ].joined(separator: \" \"))\n        case .kimiK2Token:\n            return (title, [\n                \"CodexBar will ask macOS Keychain for your Kimi K2 API key\",\n                \"so it can fetch usage. Click OK to continue.\",\n            ].joined(separator: \" \"))\n        case .minimaxCookie:\n            return (title, [\n                \"CodexBar will ask macOS Keychain for your MiniMax cookie header\",\n                \"so it can fetch usage. Click OK to continue.\",\n            ].joined(separator: \" \"))\n        case .minimaxToken:\n            return (title, [\n                \"CodexBar will ask macOS Keychain for your MiniMax API token\",\n                \"so it can fetch usage. Click OK to continue.\",\n            ].joined(separator: \" \"))\n        case .augmentCookie:\n            return (title, [\n                \"CodexBar will ask macOS Keychain for your Augment cookie header\",\n                \"so it can fetch usage. Click OK to continue.\",\n            ].joined(separator: \" \"))\n        case .ampCookie:\n            return (title, [\n                \"CodexBar will ask macOS Keychain for your Amp cookie header\",\n                \"so it can fetch usage. Click OK to continue.\",\n            ].joined(separator: \" \"))\n        }\n    }\n\n    private static func presentAlert(title: String, message: String) {\n        self.promptLock.lock()\n        defer { self.promptLock.unlock() }\n\n        if Thread.isMainThread {\n            MainActor.assumeIsolated {\n                self.showAlert(title: title, message: message)\n            }\n            return\n        }\n        DispatchQueue.main.sync {\n            MainActor.assumeIsolated {\n                self.showAlert(title: title, message: message)\n            }\n        }\n    }\n\n    @MainActor\n    private static func showAlert(title: String, message: String) {\n        let alert = NSAlert()\n        alert.messageText = title\n        alert.informativeText = message\n        alert.addButton(withTitle: \"OK\")\n        _ = alert.runModal()\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/KimiK2TokenStore.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Security\n\nprotocol KimiK2TokenStoring: Sendable {\n    func loadToken() throws -> String?\n    func storeToken(_ token: String?) throws\n}\n\nenum KimiK2TokenStoreError: LocalizedError {\n    case keychainStatus(OSStatus)\n    case invalidData\n\n    var errorDescription: String? {\n        switch self {\n        case let .keychainStatus(status):\n            \"Keychain error: \\(status)\"\n        case .invalidData:\n            \"Keychain returned invalid data.\"\n        }\n    }\n}\n\nstruct KeychainKimiK2TokenStore: KimiK2TokenStoring {\n    private static let log = CodexBarLog.logger(LogCategories.kimiK2TokenStore)\n\n    private let service = \"com.steipete.CodexBar\"\n    private let account = \"kimi-k2-api-token\"\n\n    func loadToken() throws -> String? {\n        var result: CFTypeRef?\n        let query: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: self.service,\n            kSecAttrAccount as String: self.account,\n            kSecMatchLimit as String: kSecMatchLimitOne,\n            kSecReturnData as String: true,\n        ]\n\n        if case .interactionRequired = KeychainAccessPreflight\n            .checkGenericPassword(service: self.service, account: self.account)\n        {\n            KeychainPromptHandler.handler?(KeychainPromptContext(\n                kind: .kimiK2Token,\n                service: self.service,\n                account: self.account))\n        }\n\n        let status = SecItemCopyMatching(query as CFDictionary, &result)\n        if status == errSecItemNotFound {\n            return nil\n        }\n        guard status == errSecSuccess else {\n            Self.log.error(\"Keychain read failed: \\(status)\")\n            throw KimiK2TokenStoreError.keychainStatus(status)\n        }\n\n        guard let data = result as? Data else {\n            throw KimiK2TokenStoreError.invalidData\n        }\n        let token = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines)\n        if let token, !token.isEmpty {\n            return token\n        }\n        return nil\n    }\n\n    func storeToken(_ token: String?) throws {\n        let cleaned = token?.trimmingCharacters(in: .whitespacesAndNewlines)\n        if cleaned == nil || cleaned?.isEmpty == true {\n            try self.deleteTokenIfPresent()\n            return\n        }\n\n        let data = cleaned!.data(using: .utf8)!\n        let query: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: self.service,\n            kSecAttrAccount as String: self.account,\n        ]\n        let attributes: [String: Any] = [\n            kSecValueData as String: data,\n            kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,\n        ]\n\n        let updateStatus = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)\n        if updateStatus == errSecSuccess {\n            return\n        }\n        if updateStatus != errSecItemNotFound {\n            Self.log.error(\"Keychain update failed: \\(updateStatus)\")\n            throw KimiK2TokenStoreError.keychainStatus(updateStatus)\n        }\n\n        var addQuery = query\n        for (key, value) in attributes {\n            addQuery[key] = value\n        }\n        let addStatus = SecItemAdd(addQuery as CFDictionary, nil)\n        guard addStatus == errSecSuccess else {\n            Self.log.error(\"Keychain add failed: \\(addStatus)\")\n            throw KimiK2TokenStoreError.keychainStatus(addStatus)\n        }\n    }\n\n    private func deleteTokenIfPresent() throws {\n        let query: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: self.service,\n            kSecAttrAccount as String: self.account,\n        ]\n        let status = SecItemDelete(query as CFDictionary)\n        if status == errSecSuccess || status == errSecItemNotFound {\n            return\n        }\n        Self.log.error(\"Keychain delete failed: \\(status)\")\n        throw KimiK2TokenStoreError.keychainStatus(status)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/KimiTokenStore.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Security\n\nprotocol KimiTokenStoring: Sendable {\n    func loadToken() throws -> String?\n    func storeToken(_ token: String?) throws\n}\n\nenum KimiTokenStoreError: LocalizedError {\n    case keychainStatus(OSStatus)\n    case invalidData\n\n    var errorDescription: String? {\n        switch self {\n        case let .keychainStatus(status):\n            \"Keychain error: \\(status)\"\n        case .invalidData:\n            \"Keychain returned invalid data.\"\n        }\n    }\n}\n\nstruct KeychainKimiTokenStore: KimiTokenStoring {\n    private static let log = CodexBarLog.logger(LogCategories.kimiTokenStore)\n\n    private let service = \"com.steipete.CodexBar\"\n    private let account = \"kimi-auth-token\"\n\n    func loadToken() throws -> String? {\n        guard !KeychainAccessGate.isDisabled else {\n            Self.log.debug(\"Keychain access disabled; skipping token load\")\n            return nil\n        }\n        var result: CFTypeRef?\n        let query: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: self.service,\n            kSecAttrAccount as String: self.account,\n            kSecMatchLimit as String: kSecMatchLimitOne,\n            kSecReturnData as String: true,\n        ]\n\n        if case .interactionRequired = KeychainAccessPreflight\n            .checkGenericPassword(service: self.service, account: self.account)\n        {\n            KeychainPromptHandler.handler?(KeychainPromptContext(\n                kind: .kimiToken,\n                service: self.service,\n                account: self.account))\n        }\n\n        let status = SecItemCopyMatching(query as CFDictionary, &result)\n        if status == errSecItemNotFound {\n            return nil\n        }\n        guard status == errSecSuccess else {\n            Self.log.error(\"Keychain read failed: \\(status)\")\n            throw KimiTokenStoreError.keychainStatus(status)\n        }\n\n        guard let data = result as? Data else {\n            throw KimiTokenStoreError.invalidData\n        }\n        let token = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines)\n        if let token, !token.isEmpty {\n            return token\n        }\n        return nil\n    }\n\n    func storeToken(_ token: String?) throws {\n        guard !KeychainAccessGate.isDisabled else {\n            Self.log.debug(\"Keychain access disabled; skipping token store\")\n            return\n        }\n        let cleaned = token?.trimmingCharacters(in: .whitespacesAndNewlines)\n        if cleaned == nil || cleaned?.isEmpty == true {\n            try self.deleteTokenIfPresent()\n            return\n        }\n\n        let data = cleaned!.data(using: .utf8)!\n        let query: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: self.service,\n            kSecAttrAccount as String: self.account,\n        ]\n        let attributes: [String: Any] = [\n            kSecValueData as String: data,\n            kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,\n        ]\n\n        let updateStatus = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)\n        if updateStatus == errSecSuccess {\n            return\n        }\n        if updateStatus != errSecItemNotFound {\n            Self.log.error(\"Keychain update failed: \\(updateStatus)\")\n            throw KimiTokenStoreError.keychainStatus(updateStatus)\n        }\n\n        var addQuery = query\n        for (key, value) in attributes {\n            addQuery[key] = value\n        }\n        let addStatus = SecItemAdd(addQuery as CFDictionary, nil)\n        guard addStatus == errSecSuccess else {\n            Self.log.error(\"Keychain add failed: \\(addStatus)\")\n            throw KimiTokenStoreError.keychainStatus(addStatus)\n        }\n    }\n\n    private func deleteTokenIfPresent() throws {\n        guard !KeychainAccessGate.isDisabled else { return }\n        let query: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: self.service,\n            kSecAttrAccount as String: self.account,\n        ]\n        let status = SecItemDelete(query as CFDictionary)\n        if status == errSecSuccess || status == errSecItemNotFound {\n            return\n        }\n        Self.log.error(\"Keychain delete failed: \\(status)\")\n        throw KimiTokenStoreError.keychainStatus(status)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/LaunchAtLoginManager.swift",
    "content": "import CodexBarCore\nimport ServiceManagement\n\nenum LaunchAtLoginManager {\n    private static let isRunningTests: Bool = {\n        let env = ProcessInfo.processInfo.environment\n        if env[\"XCTestConfigurationFilePath\"] != nil { return true }\n        if env[\"TESTING_LIBRARY_VERSION\"] != nil { return true }\n        if env[\"SWIFT_TESTING\"] != nil { return true }\n        return NSClassFromString(\"XCTestCase\") != nil\n    }()\n\n    static func setEnabled(_ enabled: Bool) {\n        if self.isRunningTests { return }\n        let service = SMAppService.mainApp\n        do {\n            if enabled {\n                try service.register()\n            } else {\n                try service.unregister()\n            }\n        } catch {\n            CodexBarLog.logger(LogCategories.launchAtLogin).error(\"Failed to update login item: \\(error)\")\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/LoadingPattern.swift",
    "content": "import Foundation\n\nenum LoadingPattern: String, CaseIterable, Identifiable {\n    case knightRider\n    case cylon\n    case outsideIn\n    case race\n    case pulse\n    case unbraid\n\n    var id: String {\n        self.rawValue\n    }\n\n    var displayName: String {\n        switch self {\n        case .knightRider: \"Knight Rider\"\n        case .cylon: \"Cylon\"\n        case .outsideIn: \"Outside-In\"\n        case .race: \"Race\"\n        case .pulse: \"Pulse\"\n        case .unbraid: \"Unbraid (logo → bars)\"\n        }\n    }\n\n    /// Secondary offset so the lower bar moves differently.\n    var secondaryOffset: Double {\n        switch self {\n        case .knightRider: .pi\n        case .cylon: .pi / 2\n        case .outsideIn: .pi\n        case .race: .pi / 3\n        case .pulse: .pi / 2\n        case .unbraid: .pi / 2\n        }\n    }\n\n    func value(phase: Double) -> Double {\n        let v: Double\n        switch self {\n        case .knightRider:\n            v = 0.5 + 0.5 * sin(phase) // ping-pong\n        case .cylon:\n            let t = phase.truncatingRemainder(dividingBy: .pi * 2) / (.pi * 2)\n            v = t // sawtooth 0→1\n        case .outsideIn:\n            v = abs(cos(phase)) // high at edges, dip center\n        case .race:\n            let t = (phase * 1.2).truncatingRemainder(dividingBy: .pi * 2) / (.pi * 2)\n            v = t\n        case .pulse:\n            v = 0.4 + 0.6 * (0.5 + 0.5 * sin(phase)) // 40–100%\n        case .unbraid:\n            v = 0.5 + 0.5 * sin(phase) // smooth 0→1 for morph\n        }\n        return max(0, min(v * 100, 100))\n    }\n}\n\nextension Notification.Name {\n    static let codexbarDebugReplayAllAnimations = Notification.Name(\"codexbarDebugReplayAllAnimations\")\n}\n"
  },
  {
    "path": "Sources/CodexBar/MenuBarDisplayMode.swift",
    "content": "import Foundation\n\n/// Controls what the menu bar displays when brand icon mode is enabled.\nenum MenuBarDisplayMode: String, CaseIterable, Identifiable {\n    case percent\n    case pace\n    case both\n\n    var id: String {\n        self.rawValue\n    }\n\n    var label: String {\n        switch self {\n        case .percent: \"Percent\"\n        case .pace: \"Pace\"\n        case .both: \"Both\"\n        }\n    }\n\n    var description: String {\n        switch self {\n        case .percent: \"Show remaining/used percentage (e.g. 45%)\"\n        case .pace: \"Show pace indicator (e.g. +5%)\"\n        case .both: \"Show both percentage and pace (e.g. 45% · +5%)\"\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/MenuBarDisplayText.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nenum MenuBarDisplayText {\n    static func percentText(window: RateWindow?, showUsed: Bool) -> String? {\n        guard let window else { return nil }\n        let percent = showUsed ? window.usedPercent : window.remainingPercent\n        let clamped = min(100, max(0, percent))\n        return String(format: \"%.0f%%\", clamped)\n    }\n\n    static func paceText(pace: UsagePace?) -> String? {\n        guard let pace else { return nil }\n        let deltaValue = Int(abs(pace.deltaPercent).rounded())\n        let sign = pace.deltaPercent >= 0 ? \"+\" : \"-\"\n        return \"\\(sign)\\(deltaValue)%\"\n    }\n\n    static func displayText(\n        mode: MenuBarDisplayMode,\n        percentWindow: RateWindow?,\n        pace: UsagePace? = nil,\n        showUsed: Bool) -> String?\n    {\n        switch mode {\n        case .percent:\n            return self.percentText(window: percentWindow, showUsed: showUsed)\n        case .pace:\n            return self.paceText(pace: pace)\n        case .both:\n            guard let percent = percentText(window: percentWindow, showUsed: showUsed) else { return nil }\n            let paceText: String? = Self.paceText(pace: pace)\n            guard let paceText else { return nil }\n            return \"\\(percent) · \\(paceText)\"\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/MenuCardView.swift",
    "content": "import AppKit\nimport CodexBarCore\nimport SwiftUI\n\n/// SwiftUI card used inside the NSMenu to mirror Apple's rich menu panels.\nstruct UsageMenuCardView: View {\n    struct Model {\n        enum PercentStyle: String {\n            case left\n            case used\n\n            var labelSuffix: String {\n                switch self {\n                case .left: \"left\"\n                case .used: \"used\"\n                }\n            }\n\n            var accessibilityLabel: String {\n                switch self {\n                case .left: \"Usage remaining\"\n                case .used: \"Usage used\"\n                }\n            }\n        }\n\n        struct Metric: Identifiable {\n            let id: String\n            let title: String\n            let percent: Double\n            let percentStyle: PercentStyle\n            let resetText: String?\n            let detailText: String?\n            let detailLeftText: String?\n            let detailRightText: String?\n            let pacePercent: Double?\n            let paceOnTop: Bool\n\n            var percentLabel: String {\n                String(format: \"%.0f%% %@\", self.percent, self.percentStyle.labelSuffix)\n            }\n        }\n\n        enum SubtitleStyle {\n            case info\n            case loading\n            case error\n        }\n\n        struct TokenUsageSection {\n            let sessionLine: String\n            let monthLine: String\n            let hintLine: String?\n            let errorLine: String?\n            let errorCopyText: String?\n        }\n\n        struct ProviderCostSection {\n            let title: String\n            let percentUsed: Double\n            let spendLine: String\n        }\n\n        let provider: UsageProvider\n        let providerName: String\n        let email: String\n        let subtitleText: String\n        let subtitleStyle: SubtitleStyle\n        let planText: String?\n        let metrics: [Metric]\n        let usageNotes: [String]\n        let creditsText: String?\n        let creditsRemaining: Double?\n        let creditsHintText: String?\n        let creditsHintCopyText: String?\n        let providerCost: ProviderCostSection?\n        let tokenUsage: TokenUsageSection?\n        let placeholder: String?\n        let progressColor: Color\n    }\n\n    let model: Model\n    let width: CGFloat\n    @Environment(\\.menuItemHighlighted) private var isHighlighted\n\n    static func popupMetricTitle(provider: UsageProvider, metric: Model.Metric) -> String {\n        if provider == .openrouter, metric.id == \"primary\" {\n            return \"API key limit\"\n        }\n        return metric.title\n    }\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 6) {\n            UsageMenuCardHeaderView(model: self.model)\n\n            if self.hasDetails {\n                Divider()\n            }\n\n            if self.model.metrics.isEmpty {\n                if !self.model.usageNotes.isEmpty {\n                    UsageNotesContent(notes: self.model.usageNotes)\n                } else if let placeholder = self.model.placeholder {\n                    Text(placeholder)\n                        .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))\n                        .font(.subheadline)\n                }\n            } else {\n                let hasUsage = !self.model.metrics.isEmpty || !self.model.usageNotes.isEmpty\n                let hasCredits = self.model.creditsText != nil\n                let hasProviderCost = self.model.providerCost != nil\n                let hasCost = self.model.tokenUsage != nil || hasProviderCost\n\n                VStack(alignment: .leading, spacing: 12) {\n                    if hasUsage {\n                        VStack(alignment: .leading, spacing: 12) {\n                            ForEach(self.model.metrics, id: \\.id) { metric in\n                                MetricRow(\n                                    metric: metric,\n                                    title: Self.popupMetricTitle(provider: self.model.provider, metric: metric),\n                                    progressColor: self.model.progressColor)\n                            }\n                            if !self.model.usageNotes.isEmpty {\n                                UsageNotesContent(notes: self.model.usageNotes)\n                            }\n                        }\n                    }\n                    if hasUsage, hasCredits || hasCost {\n                        Divider()\n                    }\n                    if let credits = self.model.creditsText {\n                        CreditsBarContent(\n                            creditsText: credits,\n                            creditsRemaining: self.model.creditsRemaining,\n                            hintText: self.model.creditsHintText,\n                            hintCopyText: self.model.creditsHintCopyText,\n                            progressColor: self.model.progressColor)\n                    }\n                    if hasCredits, hasCost {\n                        Divider()\n                    }\n                    if let providerCost = self.model.providerCost {\n                        ProviderCostContent(\n                            section: providerCost,\n                            progressColor: self.model.progressColor)\n                    }\n                    if hasProviderCost, self.model.tokenUsage != nil {\n                        Divider()\n                    }\n                    if let tokenUsage = self.model.tokenUsage {\n                        VStack(alignment: .leading, spacing: 6) {\n                            Text(\"Cost\")\n                                .font(.body)\n                                .fontWeight(.medium)\n                            Text(tokenUsage.sessionLine)\n                                .font(.footnote)\n                            Text(tokenUsage.monthLine)\n                                .font(.footnote)\n                            if let hint = tokenUsage.hintLine, !hint.isEmpty {\n                                Text(hint)\n                                    .font(.footnote)\n                                    .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))\n                                    .lineLimit(4)\n                                    .fixedSize(horizontal: false, vertical: true)\n                            }\n                            if let error = tokenUsage.errorLine, !error.isEmpty {\n                                Text(error)\n                                    .font(.footnote)\n                                    .foregroundStyle(MenuHighlightStyle.error(self.isHighlighted))\n                                    .lineLimit(4)\n                                    .fixedSize(horizontal: false, vertical: true)\n                                    .overlay {\n                                        ClickToCopyOverlay(copyText: tokenUsage.errorCopyText ?? error)\n                                    }\n                            }\n                        }\n                    }\n                }\n                .padding(.bottom, self.model.creditsText == nil ? 6 : 0)\n            }\n        }\n        .padding(.horizontal, 16)\n        .padding(.top, 2)\n        .padding(.bottom, 2)\n        .frame(width: self.width, alignment: .leading)\n    }\n\n    private var hasDetails: Bool {\n        !self.model.metrics.isEmpty || !self.model.usageNotes.isEmpty || self.model.placeholder != nil ||\n            self.model.tokenUsage != nil ||\n            self.model.providerCost != nil\n    }\n}\n\nprivate struct UsageMenuCardHeaderView: View {\n    let model: UsageMenuCardView.Model\n    @Environment(\\.menuItemHighlighted) private var isHighlighted\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 3) {\n            HStack(alignment: .firstTextBaseline) {\n                Text(self.model.providerName)\n                    .font(.headline)\n                    .fontWeight(.semibold)\n                Spacer()\n                Text(self.model.email)\n                    .font(.subheadline)\n                    .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))\n            }\n            let subtitleAlignment: VerticalAlignment = self.model.subtitleStyle == .error ? .top : .firstTextBaseline\n            HStack(alignment: subtitleAlignment) {\n                Text(self.model.subtitleText)\n                    .font(.footnote)\n                    .foregroundStyle(self.subtitleColor)\n                    .lineLimit(self.model.subtitleStyle == .error ? 4 : 1)\n                    .multilineTextAlignment(.leading)\n                    .fixedSize(horizontal: false, vertical: true)\n                    .layoutPriority(1)\n                    .padding(.bottom, self.model.subtitleStyle == .error ? 4 : 0)\n                Spacer()\n                if self.model.subtitleStyle == .error, !self.model.subtitleText.isEmpty {\n                    CopyIconButton(copyText: self.model.subtitleText, isHighlighted: self.isHighlighted)\n                }\n                if let plan = self.model.planText {\n                    Text(plan)\n                        .font(.footnote)\n                        .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))\n                        .lineLimit(1)\n                }\n            }\n        }\n    }\n\n    private var subtitleColor: Color {\n        switch self.model.subtitleStyle {\n        case .info: MenuHighlightStyle.secondary(self.isHighlighted)\n        case .loading: MenuHighlightStyle.secondary(self.isHighlighted)\n        case .error: MenuHighlightStyle.error(self.isHighlighted)\n        }\n    }\n}\n\nprivate struct CopyIconButtonStyle: ButtonStyle {\n    let isHighlighted: Bool\n\n    func makeBody(configuration: Configuration) -> some View {\n        configuration.label\n            .padding(4)\n            .background {\n                RoundedRectangle(cornerRadius: 4, style: .continuous)\n                    .fill(MenuHighlightStyle.secondary(self.isHighlighted).opacity(configuration.isPressed ? 0.18 : 0))\n            }\n            .scaleEffect(configuration.isPressed ? 0.94 : 1)\n            .animation(.easeOut(duration: 0.12), value: configuration.isPressed)\n    }\n}\n\nprivate struct CopyIconButton: View {\n    let copyText: String\n    let isHighlighted: Bool\n\n    @State private var didCopy = false\n    @State private var resetTask: Task<Void, Never>?\n\n    var body: some View {\n        Button {\n            self.copyToPasteboard()\n            withAnimation(.easeOut(duration: 0.12)) {\n                self.didCopy = true\n            }\n            self.resetTask?.cancel()\n            self.resetTask = Task { @MainActor in\n                try? await Task.sleep(for: .seconds(0.9))\n                withAnimation(.easeOut(duration: 0.2)) {\n                    self.didCopy = false\n                }\n            }\n        } label: {\n            Image(systemName: self.didCopy ? \"checkmark\" : \"doc.on.doc\")\n                .font(.caption2.weight(.semibold))\n                .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))\n                .frame(width: 18, height: 18)\n        }\n        .buttonStyle(CopyIconButtonStyle(isHighlighted: self.isHighlighted))\n        .accessibilityLabel(self.didCopy ? \"Copied\" : \"Copy error\")\n    }\n\n    private func copyToPasteboard() {\n        let pb = NSPasteboard.general\n        pb.clearContents()\n        pb.setString(self.copyText, forType: .string)\n    }\n}\n\nprivate struct ProviderCostContent: View {\n    let section: UsageMenuCardView.Model.ProviderCostSection\n    let progressColor: Color\n    @Environment(\\.menuItemHighlighted) private var isHighlighted\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 6) {\n            Text(self.section.title)\n                .font(.body)\n                .fontWeight(.medium)\n            UsageProgressBar(\n                percent: self.section.percentUsed,\n                tint: self.progressColor,\n                accessibilityLabel: \"Extra usage spent\")\n            HStack(alignment: .firstTextBaseline) {\n                Text(self.section.spendLine)\n                    .font(.footnote)\n                Spacer()\n                Text(String(format: \"%.0f%% used\", min(100, max(0, self.section.percentUsed))))\n                    .font(.footnote)\n                    .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))\n            }\n        }\n    }\n}\n\nprivate struct MetricRow: View {\n    let metric: UsageMenuCardView.Model.Metric\n    let title: String\n    let progressColor: Color\n    @Environment(\\.menuItemHighlighted) private var isHighlighted\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 6) {\n            Text(self.title)\n                .font(.body)\n                .fontWeight(.medium)\n            UsageProgressBar(\n                percent: self.metric.percent,\n                tint: self.progressColor,\n                accessibilityLabel: self.metric.percentStyle.accessibilityLabel,\n                pacePercent: self.metric.pacePercent,\n                paceOnTop: self.metric.paceOnTop)\n            VStack(alignment: .leading, spacing: 2) {\n                HStack(alignment: .firstTextBaseline) {\n                    Text(self.metric.percentLabel)\n                        .font(.footnote)\n                        .lineLimit(1)\n                    Spacer()\n                    if let rightLabel = self.metric.resetText {\n                        Text(rightLabel)\n                            .font(.footnote)\n                            .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))\n                            .lineLimit(1)\n                    }\n                }\n                if self.metric.detailLeftText != nil || self.metric.detailRightText != nil {\n                    HStack(alignment: .firstTextBaseline) {\n                        if let detailLeft = self.metric.detailLeftText {\n                            Text(detailLeft)\n                                .font(.footnote)\n                                .foregroundStyle(MenuHighlightStyle.primary(self.isHighlighted))\n                                .lineLimit(1)\n                        }\n                        Spacer()\n                        if let detailRight = self.metric.detailRightText {\n                            Text(detailRight)\n                                .font(.footnote)\n                                .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))\n                                .lineLimit(1)\n                        }\n                    }\n                }\n            }\n            .frame(maxWidth: .infinity, alignment: .leading)\n            if let detail = self.metric.detailText {\n                Text(detail)\n                    .font(.footnote)\n                    .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))\n                    .lineLimit(1)\n            }\n        }\n        .frame(maxWidth: .infinity, alignment: .leading)\n    }\n}\n\nprivate struct UsageNotesContent: View {\n    let notes: [String]\n    @Environment(\\.menuItemHighlighted) private var isHighlighted\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 4) {\n            ForEach(Array(self.notes.enumerated()), id: \\.offset) { _, note in\n                Text(note)\n                    .font(.footnote)\n                    .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))\n                    .lineLimit(2)\n                    .fixedSize(horizontal: false, vertical: true)\n            }\n        }\n        .frame(maxWidth: .infinity, alignment: .leading)\n    }\n}\n\nstruct UsageMenuCardHeaderSectionView: View {\n    let model: UsageMenuCardView.Model\n    let showDivider: Bool\n    let width: CGFloat\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 6) {\n            UsageMenuCardHeaderView(model: self.model)\n\n            if self.showDivider {\n                Divider()\n            }\n        }\n        .padding(.horizontal, 16)\n        .padding(.top, 2)\n        .padding(.bottom, self.model.subtitleStyle == .error ? 2 : 0)\n        .frame(width: self.width, alignment: .leading)\n    }\n}\n\nstruct UsageMenuCardUsageSectionView: View {\n    let model: UsageMenuCardView.Model\n    let showBottomDivider: Bool\n    let bottomPadding: CGFloat\n    let width: CGFloat\n    @Environment(\\.menuItemHighlighted) private var isHighlighted\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 12) {\n            if self.model.metrics.isEmpty {\n                if !self.model.usageNotes.isEmpty {\n                    UsageNotesContent(notes: self.model.usageNotes)\n                } else if let placeholder = self.model.placeholder {\n                    Text(placeholder)\n                        .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))\n                        .font(.subheadline)\n                }\n            } else {\n                ForEach(self.model.metrics, id: \\.id) { metric in\n                    MetricRow(\n                        metric: metric,\n                        title: UsageMenuCardView.popupMetricTitle(provider: self.model.provider, metric: metric),\n                        progressColor: self.model.progressColor)\n                }\n                if !self.model.usageNotes.isEmpty {\n                    UsageNotesContent(notes: self.model.usageNotes)\n                }\n            }\n            if self.showBottomDivider {\n                Divider()\n            }\n        }\n        .padding(.horizontal, 16)\n        .padding(.top, 10)\n        .padding(.bottom, self.bottomPadding)\n        .frame(width: self.width, alignment: .leading)\n    }\n}\n\nstruct UsageMenuCardCreditsSectionView: View {\n    let model: UsageMenuCardView.Model\n    let showBottomDivider: Bool\n    let topPadding: CGFloat\n    let bottomPadding: CGFloat\n    let width: CGFloat\n\n    var body: some View {\n        if let credits = self.model.creditsText {\n            VStack(alignment: .leading, spacing: 6) {\n                CreditsBarContent(\n                    creditsText: credits,\n                    creditsRemaining: self.model.creditsRemaining,\n                    hintText: self.model.creditsHintText,\n                    hintCopyText: self.model.creditsHintCopyText,\n                    progressColor: self.model.progressColor)\n                if self.showBottomDivider {\n                    Divider()\n                }\n            }\n            .padding(.horizontal, 16)\n            .padding(.top, self.topPadding)\n            .padding(.bottom, self.bottomPadding)\n            .frame(width: self.width, alignment: .leading)\n        }\n    }\n}\n\nprivate struct CreditsBarContent: View {\n    private static let fullScaleTokens: Double = 1000\n\n    let creditsText: String\n    let creditsRemaining: Double?\n    let hintText: String?\n    let hintCopyText: String?\n    let progressColor: Color\n    @Environment(\\.menuItemHighlighted) private var isHighlighted\n\n    private var percentLeft: Double? {\n        guard let creditsRemaining else { return nil }\n        let percent = (creditsRemaining / Self.fullScaleTokens) * 100\n        return min(100, max(0, percent))\n    }\n\n    private var scaleText: String {\n        let scale = UsageFormatter.tokenCountString(Int(Self.fullScaleTokens))\n        return \"\\(scale) tokens\"\n    }\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 6) {\n            Text(\"Credits\")\n                .font(.body)\n                .fontWeight(.medium)\n            if let percentLeft {\n                UsageProgressBar(\n                    percent: percentLeft,\n                    tint: self.progressColor,\n                    accessibilityLabel: \"Credits remaining\")\n                HStack(alignment: .firstTextBaseline) {\n                    Text(self.creditsText)\n                        .font(.caption)\n                    Spacer()\n                    Text(self.scaleText)\n                        .font(.caption)\n                        .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))\n                }\n            } else {\n                Text(self.creditsText)\n                    .font(.caption)\n            }\n            if let hintText, !hintText.isEmpty {\n                Text(hintText)\n                    .font(.footnote)\n                    .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))\n                    .lineLimit(4)\n                    .fixedSize(horizontal: false, vertical: true)\n                    .overlay {\n                        ClickToCopyOverlay(copyText: self.hintCopyText ?? hintText)\n                    }\n            }\n        }\n    }\n}\n\nstruct UsageMenuCardCostSectionView: View {\n    let model: UsageMenuCardView.Model\n    let topPadding: CGFloat\n    let bottomPadding: CGFloat\n    let width: CGFloat\n    @Environment(\\.menuItemHighlighted) private var isHighlighted\n\n    var body: some View {\n        let hasTokenCost = self.model.tokenUsage != nil\n        return Group {\n            if hasTokenCost {\n                VStack(alignment: .leading, spacing: 10) {\n                    if let tokenUsage = self.model.tokenUsage {\n                        VStack(alignment: .leading, spacing: 6) {\n                            Text(\"Cost\")\n                                .font(.body)\n                                .fontWeight(.medium)\n                            Text(tokenUsage.sessionLine)\n                                .font(.caption)\n                            Text(tokenUsage.monthLine)\n                                .font(.caption)\n                            if let hint = tokenUsage.hintLine, !hint.isEmpty {\n                                Text(hint)\n                                    .font(.footnote)\n                                    .foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))\n                                    .lineLimit(4)\n                                    .fixedSize(horizontal: false, vertical: true)\n                            }\n                            if let error = tokenUsage.errorLine, !error.isEmpty {\n                                Text(error)\n                                    .font(.footnote)\n                                    .foregroundStyle(MenuHighlightStyle.error(self.isHighlighted))\n                                    .lineLimit(4)\n                                    .fixedSize(horizontal: false, vertical: true)\n                                    .overlay {\n                                        ClickToCopyOverlay(copyText: tokenUsage.errorCopyText ?? error)\n                                    }\n                            }\n                        }\n                    }\n                }\n                .padding(.horizontal, 16)\n                .padding(.top, self.topPadding)\n                .padding(.bottom, self.bottomPadding)\n                .frame(width: self.width, alignment: .leading)\n            }\n        }\n    }\n}\n\nstruct UsageMenuCardExtraUsageSectionView: View {\n    let model: UsageMenuCardView.Model\n    let topPadding: CGFloat\n    let bottomPadding: CGFloat\n    let width: CGFloat\n\n    var body: some View {\n        Group {\n            if let providerCost = self.model.providerCost {\n                ProviderCostContent(\n                    section: providerCost,\n                    progressColor: self.model.progressColor)\n                    .padding(.horizontal, 16)\n                    .padding(.top, self.topPadding)\n                    .padding(.bottom, self.bottomPadding)\n                    .frame(width: self.width, alignment: .leading)\n            }\n        }\n    }\n}\n\n// MARK: - Model factory\n\nextension UsageMenuCardView.Model {\n    struct Input {\n        let provider: UsageProvider\n        let metadata: ProviderMetadata\n        let snapshot: UsageSnapshot?\n        let credits: CreditsSnapshot?\n        let creditsError: String?\n        let dashboard: OpenAIDashboardSnapshot?\n        let dashboardError: String?\n        let tokenSnapshot: CostUsageTokenSnapshot?\n        let tokenError: String?\n        let account: AccountInfo\n        let isRefreshing: Bool\n        let lastError: String?\n        let usageBarsShowUsed: Bool\n        let resetTimeDisplayStyle: ResetTimeDisplayStyle\n        let tokenCostUsageEnabled: Bool\n        let showOptionalCreditsAndExtraUsage: Bool\n        let sourceLabel: String?\n        let kiloAutoMode: Bool\n        let hidePersonalInfo: Bool\n        let weeklyPace: UsagePace?\n        let now: Date\n\n        init(\n            provider: UsageProvider,\n            metadata: ProviderMetadata,\n            snapshot: UsageSnapshot?,\n            credits: CreditsSnapshot?,\n            creditsError: String?,\n            dashboard: OpenAIDashboardSnapshot?,\n            dashboardError: String?,\n            tokenSnapshot: CostUsageTokenSnapshot?,\n            tokenError: String?,\n            account: AccountInfo,\n            isRefreshing: Bool,\n            lastError: String?,\n            usageBarsShowUsed: Bool,\n            resetTimeDisplayStyle: ResetTimeDisplayStyle,\n            tokenCostUsageEnabled: Bool,\n            showOptionalCreditsAndExtraUsage: Bool,\n            sourceLabel: String? = nil,\n            kiloAutoMode: Bool = false,\n            hidePersonalInfo: Bool,\n            weeklyPace: UsagePace? = nil,\n            now: Date)\n        {\n            self.provider = provider\n            self.metadata = metadata\n            self.snapshot = snapshot\n            self.credits = credits\n            self.creditsError = creditsError\n            self.dashboard = dashboard\n            self.dashboardError = dashboardError\n            self.tokenSnapshot = tokenSnapshot\n            self.tokenError = tokenError\n            self.account = account\n            self.isRefreshing = isRefreshing\n            self.lastError = lastError\n            self.usageBarsShowUsed = usageBarsShowUsed\n            self.resetTimeDisplayStyle = resetTimeDisplayStyle\n            self.tokenCostUsageEnabled = tokenCostUsageEnabled\n            self.showOptionalCreditsAndExtraUsage = showOptionalCreditsAndExtraUsage\n            self.sourceLabel = sourceLabel\n            self.kiloAutoMode = kiloAutoMode\n            self.hidePersonalInfo = hidePersonalInfo\n            self.weeklyPace = weeklyPace\n            self.now = now\n        }\n    }\n\n    static func make(_ input: Input) -> UsageMenuCardView.Model {\n        let planText = Self.plan(\n            for: input.provider,\n            snapshot: input.snapshot,\n            account: input.account,\n            metadata: input.metadata)\n        let metrics = Self.metrics(input: input)\n        let usageNotes = Self.usageNotes(input: input)\n        let creditsText: String? = if input.provider == .openrouter {\n            nil\n        } else if input.provider == .codex, !input.showOptionalCreditsAndExtraUsage {\n            nil\n        } else {\n            Self.creditsLine(metadata: input.metadata, credits: input.credits, error: input.creditsError)\n        }\n        let providerCost: ProviderCostSection? = if input.provider == .claude, !input.showOptionalCreditsAndExtraUsage {\n            nil\n        } else {\n            Self.providerCostSection(provider: input.provider, cost: input.snapshot?.providerCost)\n        }\n        let tokenUsage = Self.tokenUsageSection(\n            provider: input.provider,\n            enabled: input.tokenCostUsageEnabled,\n            snapshot: input.tokenSnapshot,\n            error: input.tokenError)\n        let subtitle = Self.subtitle(\n            snapshot: input.snapshot,\n            isRefreshing: input.isRefreshing,\n            lastError: input.lastError)\n        let redacted = Self.redactedText(input: input, subtitle: subtitle)\n        let placeholder = input.snapshot == nil && !input.isRefreshing && input.lastError == nil ? \"No usage yet\" : nil\n\n        return UsageMenuCardView.Model(\n            provider: input.provider,\n            providerName: input.metadata.displayName,\n            email: redacted.email,\n            subtitleText: redacted.subtitleText,\n            subtitleStyle: subtitle.style,\n            planText: planText,\n            metrics: metrics,\n            usageNotes: usageNotes,\n            creditsText: creditsText,\n            creditsRemaining: input.credits?.remaining,\n            creditsHintText: redacted.creditsHintText,\n            creditsHintCopyText: redacted.creditsHintCopyText,\n            providerCost: providerCost,\n            tokenUsage: tokenUsage,\n            placeholder: placeholder,\n            progressColor: Self.progressColor(for: input.provider))\n    }\n\n    private static func usageNotes(input: Input) -> [String] {\n        if input.provider == .kilo {\n            var notes = Self.kiloLoginDetails(snapshot: input.snapshot)\n            let resolvedSource = input.sourceLabel?\n                .trimmingCharacters(in: .whitespacesAndNewlines)\n                .lowercased()\n            if input.kiloAutoMode,\n               resolvedSource == \"cli\",\n               !notes.contains(where: { $0.caseInsensitiveCompare(\"Using CLI fallback\") == .orderedSame })\n            {\n                notes.append(\"Using CLI fallback\")\n            }\n            return notes\n        }\n\n        guard input.provider == .openrouter,\n              let openRouter = input.snapshot?.openRouterUsage\n        else {\n            return []\n        }\n\n        return switch openRouter.keyQuotaStatus {\n        case .available: []\n        case .noLimitConfigured: [\"No limit set for the API key\"]\n        case .unavailable: [\"API key limit unavailable right now\"]\n        }\n    }\n\n    private static func email(\n        for provider: UsageProvider,\n        snapshot: UsageSnapshot?,\n        account: AccountInfo,\n        metadata: ProviderMetadata) -> String\n    {\n        if let email = snapshot?.accountEmail(for: provider), !email.isEmpty { return email }\n        if metadata.usesAccountFallback,\n           let email = account.email, !email.isEmpty\n        {\n            return email\n        }\n        return \"\"\n    }\n\n    private static func plan(\n        for provider: UsageProvider,\n        snapshot: UsageSnapshot?,\n        account: AccountInfo,\n        metadata: ProviderMetadata) -> String?\n    {\n        if provider == .kilo {\n            guard let pass = self.kiloLoginPass(snapshot: snapshot) else {\n                return nil\n            }\n            return self.planDisplay(pass)\n        }\n        if let plan = snapshot?.loginMethod(for: provider), !plan.isEmpty {\n            return self.planDisplay(plan)\n        }\n        if metadata.usesAccountFallback,\n           let plan = account.plan, !plan.isEmpty\n        {\n            return Self.planDisplay(plan)\n        }\n        return nil\n    }\n\n    private static func planDisplay(_ text: String) -> String {\n        let cleaned = UsageFormatter.cleanPlanName(text)\n        return cleaned.isEmpty ? text : cleaned\n    }\n\n    private static func kiloLoginPass(snapshot: UsageSnapshot?) -> String? {\n        self.kiloLoginParts(snapshot: snapshot).pass\n    }\n\n    private static func kiloLoginDetails(snapshot: UsageSnapshot?) -> [String] {\n        self.kiloLoginParts(snapshot: snapshot).details\n    }\n\n    private static func kiloLoginParts(snapshot: UsageSnapshot?) -> (pass: String?, details: [String]) {\n        guard let loginMethod = snapshot?.loginMethod(for: .kilo) else {\n            return (nil, [])\n        }\n        let parts = loginMethod\n            .components(separatedBy: \"·\")\n            .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }\n            .filter { !$0.isEmpty }\n        guard !parts.isEmpty else {\n            return (nil, [])\n        }\n        let first = parts[0]\n        if self.isKiloActivitySegment(first) {\n            return (nil, parts)\n        }\n        return (first, Array(parts.dropFirst()))\n    }\n\n    private static func isKiloActivitySegment(_ text: String) -> Bool {\n        let normalized = text.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()\n        return normalized.hasPrefix(\"auto top-up:\")\n    }\n\n    private static func subtitle(\n        snapshot: UsageSnapshot?,\n        isRefreshing: Bool,\n        lastError: String?) -> (text: String, style: SubtitleStyle)\n    {\n        if let lastError, !lastError.isEmpty {\n            return (lastError.trimmingCharacters(in: .whitespacesAndNewlines), .error)\n        }\n\n        if isRefreshing, snapshot == nil {\n            return (\"Refreshing...\", .loading)\n        }\n\n        if let updated = snapshot?.updatedAt {\n            return (UsageFormatter.updatedString(from: updated), .info)\n        }\n\n        return (\"Not fetched yet\", .info)\n    }\n\n    private struct RedactedText {\n        let email: String\n        let subtitleText: String\n        let creditsHintText: String?\n        let creditsHintCopyText: String?\n    }\n\n    private static func redactedText(\n        input: Input,\n        subtitle: (text: String, style: SubtitleStyle)) -> RedactedText\n    {\n        let email = PersonalInfoRedactor.redactEmail(\n            Self.email(\n                for: input.provider,\n                snapshot: input.snapshot,\n                account: input.account,\n                metadata: input.metadata),\n            isEnabled: input.hidePersonalInfo)\n        let subtitleText = PersonalInfoRedactor.redactEmails(in: subtitle.text, isEnabled: input.hidePersonalInfo)\n            ?? subtitle.text\n        let creditsHintText = PersonalInfoRedactor.redactEmails(\n            in: Self.dashboardHint(provider: input.provider, error: input.dashboardError),\n            isEnabled: input.hidePersonalInfo)\n        let creditsHintCopyText = Self.creditsHintCopyText(\n            dashboardError: input.dashboardError,\n            hidePersonalInfo: input.hidePersonalInfo)\n        return RedactedText(\n            email: email,\n            subtitleText: subtitleText,\n            creditsHintText: creditsHintText,\n            creditsHintCopyText: creditsHintCopyText)\n    }\n\n    private static func creditsHintCopyText(dashboardError: String?, hidePersonalInfo: Bool) -> String? {\n        guard let dashboardError, !dashboardError.isEmpty else { return nil }\n        return hidePersonalInfo ? \"\" : dashboardError\n    }\n\n    private static func metrics(input: Input) -> [Metric] {\n        guard let snapshot = input.snapshot else { return [] }\n        var metrics: [Metric] = []\n        let percentStyle: PercentStyle = input.usageBarsShowUsed ? .used : .left\n        let zaiUsage = input.provider == .zai ? snapshot.zaiUsage : nil\n        let zaiTokenDetail = Self.zaiLimitDetailText(limit: zaiUsage?.tokenLimit)\n        let zaiTimeDetail = Self.zaiLimitDetailText(limit: zaiUsage?.timeLimit)\n        let openRouterQuotaDetail = Self.openRouterQuotaDetail(provider: input.provider, snapshot: snapshot)\n        if let primary = snapshot.primary {\n            var primaryDetailText: String? = input.provider == .zai ? zaiTokenDetail : nil\n            var primaryResetText = Self.resetText(for: primary, style: input.resetTimeDisplayStyle, now: input.now)\n            if input.provider == .openrouter,\n               let openRouterQuotaDetail\n            {\n                primaryResetText = openRouterQuotaDetail\n            }\n            if input.provider == .warp || input.provider == .kilo,\n               let detail = primary.resetDescription,\n               !detail.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty\n            {\n                primaryDetailText = detail\n            }\n            if input.provider == .alibaba,\n               let detail = primary.resetDescription,\n               !detail.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty\n            {\n                primaryDetailText = detail\n            }\n            if input.provider == .warp || input.provider == .kilo, primary.resetsAt == nil {\n                primaryResetText = nil\n            }\n            metrics.append(Metric(\n                id: \"primary\",\n                title: input.metadata.sessionLabel,\n                percent: Self.clamped(\n                    input.usageBarsShowUsed ? primary.usedPercent : primary.remainingPercent),\n                percentStyle: percentStyle,\n                resetText: primaryResetText,\n                detailText: primaryDetailText,\n                detailLeftText: nil,\n                detailRightText: nil,\n                pacePercent: nil,\n                paceOnTop: true))\n        }\n        if let weekly = snapshot.secondary {\n            let paceDetail = Self.weeklyPaceDetail(\n                window: weekly,\n                now: input.now,\n                pace: input.weeklyPace,\n                showUsed: input.usageBarsShowUsed)\n            var weeklyResetText = Self.resetText(for: weekly, style: input.resetTimeDisplayStyle, now: input.now)\n            var weeklyDetailText: String? = input.provider == .zai ? zaiTimeDetail : nil\n            if input.provider == .warp,\n               let detail = weekly.resetDescription,\n               !detail.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty\n            {\n                weeklyResetText = nil\n                weeklyDetailText = detail\n            }\n            if input.provider == .kilo,\n               let detail = weekly.resetDescription,\n               !detail.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty\n            {\n                weeklyDetailText = detail\n                if weekly.resetsAt == nil {\n                    weeklyResetText = nil\n                }\n            }\n            if input.provider == .alibaba,\n               let detail = weekly.resetDescription,\n               !detail.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty\n            {\n                weeklyDetailText = detail\n            }\n            metrics.append(Metric(\n                id: \"secondary\",\n                title: input.metadata.weeklyLabel,\n                percent: Self.clamped(input.usageBarsShowUsed ? weekly.usedPercent : weekly.remainingPercent),\n                percentStyle: percentStyle,\n                resetText: weeklyResetText,\n                detailText: weeklyDetailText,\n                detailLeftText: paceDetail?.leftLabel,\n                detailRightText: paceDetail?.rightLabel,\n                pacePercent: paceDetail?.pacePercent,\n                paceOnTop: paceDetail?.paceOnTop ?? true))\n        }\n        if input.provider == .kilo,\n           metrics.contains(where: { $0.id == \"primary\" }),\n           metrics.contains(where: { $0.id == \"secondary\" })\n        {\n            metrics.sort { lhs, rhs in\n                let kiloOrder: [String: Int] = [\n                    \"secondary\": 0,\n                    \"primary\": 1,\n                ]\n                return (kiloOrder[lhs.id] ?? Int.max) < (kiloOrder[rhs.id] ?? Int.max)\n            }\n        }\n        if input.metadata.supportsOpus, let opus = snapshot.tertiary {\n            var tertiaryDetailText: String?\n            if input.provider == .alibaba,\n               let detail = opus.resetDescription,\n               !detail.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty\n            {\n                tertiaryDetailText = detail\n            }\n            metrics.append(Metric(\n                id: \"tertiary\",\n                title: input.metadata.opusLabel ?? \"Sonnet\",\n                percent: Self.clamped(input.usageBarsShowUsed ? opus.usedPercent : opus.remainingPercent),\n                percentStyle: percentStyle,\n                resetText: Self.resetText(for: opus, style: input.resetTimeDisplayStyle, now: input.now),\n                detailText: tertiaryDetailText,\n                detailLeftText: nil,\n                detailRightText: nil,\n                pacePercent: nil,\n                paceOnTop: true))\n        }\n\n        if input.provider == .codex, let remaining = input.dashboard?.codeReviewRemainingPercent {\n            let percent = input.usageBarsShowUsed ? (100 - remaining) : remaining\n            metrics.append(Metric(\n                id: \"code-review\",\n                title: \"Code review\",\n                percent: Self.clamped(percent),\n                percentStyle: percentStyle,\n                resetText: nil,\n                detailText: nil,\n                detailLeftText: nil,\n                detailRightText: nil,\n                pacePercent: nil,\n                paceOnTop: true))\n        }\n        return metrics\n    }\n\n    private static func zaiLimitDetailText(limit: ZaiLimitEntry?) -> String? {\n        guard let limit else { return nil }\n\n        if let currentValue = limit.currentValue,\n           let usage = limit.usage,\n           let remaining = limit.remaining\n        {\n            let currentStr = UsageFormatter.tokenCountString(currentValue)\n            let usageStr = UsageFormatter.tokenCountString(usage)\n            let remainingStr = UsageFormatter.tokenCountString(remaining)\n            return \"\\(currentStr) / \\(usageStr) (\\(remainingStr) remaining)\"\n        }\n\n        return nil\n    }\n\n    private static func openRouterQuotaDetail(provider: UsageProvider, snapshot: UsageSnapshot) -> String? {\n        guard provider == .openrouter,\n              let usage = snapshot.openRouterUsage,\n              usage.hasValidKeyQuota,\n              let keyRemaining = usage.keyRemaining,\n              let keyLimit = usage.keyLimit\n        else {\n            return nil\n        }\n\n        let remaining = UsageFormatter.usdString(keyRemaining)\n        let limit = UsageFormatter.usdString(keyLimit)\n        return \"\\(remaining)/\\(limit) left\"\n    }\n\n    private struct PaceDetail {\n        let leftLabel: String\n        let rightLabel: String?\n        let pacePercent: Double?\n        let paceOnTop: Bool\n    }\n\n    private static func weeklyPaceDetail(\n        window: RateWindow,\n        now: Date,\n        pace: UsagePace?,\n        showUsed: Bool) -> PaceDetail?\n    {\n        guard let pace else { return nil }\n        let detail = UsagePaceText.weeklyDetail(pace: pace, now: now)\n        let expectedUsed = detail.expectedUsedPercent\n        let actualUsed = window.usedPercent\n        let expectedPercent = showUsed ? expectedUsed : (100 - expectedUsed)\n        let actualPercent = showUsed ? actualUsed : (100 - actualUsed)\n        if expectedPercent.isFinite == false || actualPercent.isFinite == false { return nil }\n        let paceOnTop = actualUsed <= expectedUsed\n        let pacePercent: Double? = if detail.stage == .onTrack { nil } else { expectedPercent }\n        return PaceDetail(\n            leftLabel: detail.leftLabel,\n            rightLabel: detail.rightLabel,\n            pacePercent: pacePercent,\n            paceOnTop: paceOnTop)\n    }\n\n    private static func creditsLine(\n        metadata: ProviderMetadata,\n        credits: CreditsSnapshot?,\n        error: String?) -> String?\n    {\n        guard metadata.supportsCredits else { return nil }\n        if let credits {\n            return UsageFormatter.creditsString(from: credits.remaining)\n        }\n        if let error, !error.isEmpty {\n            return error.trimmingCharacters(in: .whitespacesAndNewlines)\n        }\n        return metadata.creditsHint\n    }\n\n    private static func dashboardHint(provider: UsageProvider, error: String?) -> String? {\n        guard provider == .codex else { return nil }\n        guard let error, !error.isEmpty else { return nil }\n        return error\n    }\n\n    private static func tokenUsageSection(\n        provider: UsageProvider,\n        enabled: Bool,\n        snapshot: CostUsageTokenSnapshot?,\n        error: String?) -> TokenUsageSection?\n    {\n        guard provider == .codex || provider == .claude || provider == .vertexai else { return nil }\n        guard enabled else { return nil }\n        guard let snapshot else { return nil }\n\n        let sessionCost = snapshot.sessionCostUSD.map { UsageFormatter.usdString($0) } ?? \"—\"\n        let sessionTokens = snapshot.sessionTokens.map { UsageFormatter.tokenCountString($0) }\n        let sessionLine: String = {\n            if let sessionTokens {\n                return \"Today: \\(sessionCost) · \\(sessionTokens) tokens\"\n            }\n            return \"Today: \\(sessionCost)\"\n        }()\n\n        let monthCost = snapshot.last30DaysCostUSD.map { UsageFormatter.usdString($0) } ?? \"—\"\n        let fallbackTokens = snapshot.daily.compactMap(\\.totalTokens).reduce(0, +)\n        let monthTokensValue = snapshot.last30DaysTokens ?? (fallbackTokens > 0 ? fallbackTokens : nil)\n        let monthTokens = monthTokensValue.map { UsageFormatter.tokenCountString($0) }\n        let monthLine: String = {\n            if let monthTokens {\n                return \"Last 30 days: \\(monthCost) · \\(monthTokens) tokens\"\n            }\n            return \"Last 30 days: \\(monthCost)\"\n        }()\n        let err = (error?.isEmpty ?? true) ? nil : error\n        return TokenUsageSection(\n            sessionLine: sessionLine,\n            monthLine: monthLine,\n            hintLine: nil,\n            errorLine: err,\n            errorCopyText: (error?.isEmpty ?? true) ? nil : error)\n    }\n\n    private static func providerCostSection(\n        provider: UsageProvider,\n        cost: ProviderCostSnapshot?) -> ProviderCostSection?\n    {\n        guard let cost else { return nil }\n        guard cost.limit > 0 else { return nil }\n\n        let used: String\n        let limit: String\n        let title: String\n\n        if cost.currencyCode == \"Quota\" {\n            title = \"Quota usage\"\n            used = String(format: \"%.0f\", cost.used)\n            limit = String(format: \"%.0f\", cost.limit)\n        } else {\n            title = \"Extra usage\"\n            used = UsageFormatter.currencyString(cost.used, currencyCode: cost.currencyCode)\n            limit = UsageFormatter.currencyString(cost.limit, currencyCode: cost.currencyCode)\n        }\n\n        let percentUsed = Self.clamped((cost.used / cost.limit) * 100)\n        let periodLabel = cost.period ?? \"This month\"\n\n        return ProviderCostSection(\n            title: title,\n            percentUsed: percentUsed,\n            spendLine: \"\\(periodLabel): \\(used) / \\(limit)\")\n    }\n\n    private static func clamped(_ value: Double) -> Double {\n        min(100, max(0, value))\n    }\n\n    private static func progressColor(for provider: UsageProvider) -> Color {\n        let color = ProviderDescriptorRegistry.descriptor(for: provider).branding.color\n        return Color(red: color.red, green: color.green, blue: color.blue)\n    }\n\n    private static func resetText(\n        for window: RateWindow,\n        style: ResetTimeDisplayStyle,\n        now: Date) -> String?\n    {\n        UsageFormatter.resetLine(for: window, style: style, now: now)\n    }\n}\n\n// MARK: - Copy-on-click overlay\n\nprivate struct ClickToCopyOverlay: NSViewRepresentable {\n    let copyText: String\n\n    func makeNSView(context: Context) -> ClickToCopyView {\n        ClickToCopyView(copyText: self.copyText)\n    }\n\n    func updateNSView(_ nsView: ClickToCopyView, context: Context) {\n        nsView.copyText = self.copyText\n    }\n}\n\nprivate final class ClickToCopyView: NSView {\n    var copyText: String\n\n    init(copyText: String) {\n        self.copyText = copyText\n        super.init(frame: .zero)\n        self.wantsLayer = false\n    }\n\n    @available(*, unavailable)\n    required init?(coder: NSCoder) {\n        fatalError(\"init(coder:) has not been implemented\")\n    }\n\n    override func acceptsFirstMouse(for event: NSEvent?) -> Bool {\n        true\n    }\n\n    override func mouseDown(with event: NSEvent) {\n        _ = event\n        let pb = NSPasteboard.general\n        pb.clearContents()\n        pb.setString(self.copyText, forType: .string)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/MenuContent.swift",
    "content": "import AppKit\nimport CodexBarCore\nimport SwiftUI\n\n@MainActor\nstruct MenuContent: View {\n    @Bindable var store: UsageStore\n    @Bindable var settings: SettingsStore\n    let account: AccountInfo\n    let updater: UpdaterProviding\n    let provider: UsageProvider?\n    let actions: MenuActions\n\n    var body: some View {\n        let descriptor = MenuDescriptor.build(\n            provider: self.provider,\n            store: self.store,\n            settings: self.settings,\n            account: self.account,\n            updateReady: self.updater.updateStatus.isUpdateReady)\n\n        VStack(alignment: .leading, spacing: 8) {\n            ForEach(Array(descriptor.sections.enumerated()), id: \\.offset) { index, section in\n                VStack(alignment: .leading, spacing: 4) {\n                    ForEach(Array(section.entries.enumerated()), id: \\.offset) { _, entry in\n                        self.row(for: entry)\n                    }\n                }\n                if index < descriptor.sections.count - 1 {\n                    Divider()\n                }\n            }\n        }\n        .padding(.horizontal, 10)\n        .padding(.vertical, 6)\n        .frame(minWidth: 260, alignment: .leading)\n    }\n\n    @ViewBuilder\n    private func row(for entry: MenuDescriptor.Entry) -> some View {\n        switch entry {\n        case let .text(text, style):\n            switch style {\n            case .headline:\n                Text(text).font(.headline)\n            case .primary:\n                Text(text)\n            case .secondary:\n                Text(text).foregroundStyle(.secondary).font(.footnote)\n            }\n        case let .action(title, action):\n            Button {\n                self.perform(action)\n            } label: {\n                if let icon = self.iconName(for: action) {\n                    HStack(spacing: 8) {\n                        Image(systemName: icon)\n                            .imageScale(.medium)\n                            .frame(width: 18, alignment: .center)\n                        Text(title)\n                    }\n                    .foregroundStyle(.primary)\n                } else {\n                    Text(title)\n                }\n            }\n            .buttonStyle(.plain)\n        case .divider:\n            Divider()\n        }\n    }\n\n    private func iconName(for action: MenuDescriptor.MenuAction) -> String? {\n        action.systemImageName\n    }\n\n    private func perform(_ action: MenuDescriptor.MenuAction) {\n        switch action {\n        case .refresh:\n            self.actions.refresh()\n        case .refreshAugmentSession:\n            self.actions.refreshAugmentSession()\n        case .installUpdate:\n            self.actions.installUpdate()\n        case .dashboard:\n            self.actions.openDashboard()\n        case .statusPage:\n            self.actions.openStatusPage()\n        case let .switchAccount(provider):\n            self.actions.switchAccount(provider)\n        case let .openTerminal(command):\n            self.actions.openTerminal(command)\n        case let .loginToProvider(url):\n            if let urlObj = URL(string: url) {\n                NSWorkspace.shared.open(urlObj)\n            }\n        case .settings:\n            self.actions.openSettings()\n        case .about:\n            self.actions.openAbout()\n        case .quit:\n            self.actions.quit()\n        case let .copyError(message):\n            self.actions.copyError(message)\n        }\n    }\n}\n\nstruct MenuActions {\n    let installUpdate: () -> Void\n    let refresh: () -> Void\n    let refreshAugmentSession: () -> Void\n    let openDashboard: () -> Void\n    let openStatusPage: () -> Void\n    let switchAccount: (UsageProvider) -> Void\n    let openTerminal: (String) -> Void\n    let openSettings: () -> Void\n    let openAbout: () -> Void\n    let quit: () -> Void\n    let copyError: (String) -> Void\n}\n\n@MainActor\nstruct StatusIconView: View {\n    @Bindable var store: UsageStore\n    let provider: UsageProvider\n\n    var body: some View {\n        Image(nsImage: self.icon)\n            .renderingMode(.template)\n            .interpolation(.none)\n    }\n\n    private var icon: NSImage {\n        IconRenderer.makeIcon(\n            primaryRemaining: self.store.snapshot(for: self.provider)?.primary?.remainingPercent,\n            weeklyRemaining: self.store.snapshot(for: self.provider)?.secondary?.remainingPercent,\n            creditsRemaining: self.provider == .codex ? self.store.credits?.remaining : nil,\n            stale: self.store.isStale(provider: self.provider),\n            style: self.store.style(for: self.provider),\n            statusIndicator: self.store.statusIndicator(for: self.provider))\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/MenuDescriptor.swift",
    "content": "import CodexBarCore\nimport Foundation\n\n@MainActor\nstruct MenuDescriptor {\n    struct Section {\n        var entries: [Entry]\n    }\n\n    enum Entry {\n        case text(String, TextStyle)\n        case action(String, MenuAction)\n        case divider\n    }\n\n    enum MenuActionSystemImage: String {\n        case refresh = \"arrow.clockwise\"\n        case dashboard = \"chart.bar\"\n        case statusPage = \"waveform.path.ecg\"\n        case switchAccount = \"key\"\n        case openTerminal = \"terminal\"\n        case loginToProvider = \"arrow.right.square\"\n        case settings = \"gearshape\"\n        case about = \"info.circle\"\n        case quit = \"xmark.rectangle\"\n        case copyError = \"doc.on.doc\"\n    }\n\n    enum TextStyle {\n        case headline\n        case primary\n        case secondary\n    }\n\n    enum MenuAction {\n        case installUpdate\n        case refresh\n        case refreshAugmentSession\n        case dashboard\n        case statusPage\n        case switchAccount(UsageProvider)\n        case openTerminal(command: String)\n        case loginToProvider(url: String)\n        case settings\n        case about\n        case quit\n        case copyError(String)\n    }\n\n    var sections: [Section]\n\n    static func build(\n        provider: UsageProvider?,\n        store: UsageStore,\n        settings: SettingsStore,\n        account: AccountInfo,\n        updateReady: Bool,\n        includeContextualActions: Bool = true) -> MenuDescriptor\n    {\n        var sections: [Section] = []\n\n        if let provider {\n            sections.append(Self.usageSection(for: provider, store: store, settings: settings))\n            if let accountSection = Self.accountSection(\n                for: provider,\n                store: store,\n                settings: settings,\n                account: account)\n            {\n                sections.append(accountSection)\n            }\n        } else {\n            var addedUsage = false\n\n            for enabledProvider in store.enabledProviders() {\n                sections.append(Self.usageSection(for: enabledProvider, store: store, settings: settings))\n                addedUsage = true\n            }\n            if addedUsage {\n                if let accountProvider = Self.accountProviderForCombined(store: store),\n                   let accountSection = Self.accountSection(\n                       for: accountProvider,\n                       store: store,\n                       settings: settings,\n                       account: account)\n                {\n                    sections.append(accountSection)\n                }\n            } else {\n                sections.append(Section(entries: [.text(\"No usage configured.\", .secondary)]))\n            }\n        }\n\n        if includeContextualActions {\n            let actions = Self.actionsSection(for: provider, store: store, account: account)\n            if !actions.entries.isEmpty {\n                sections.append(actions)\n            }\n        }\n        sections.append(Self.metaSection(updateReady: updateReady))\n\n        return MenuDescriptor(sections: sections)\n    }\n\n    private static func usageSection(\n        for provider: UsageProvider,\n        store: UsageStore,\n        settings: SettingsStore) -> Section\n    {\n        let meta = store.metadata(for: provider)\n        var entries: [Entry] = []\n        let headlineText: String = {\n            if let ver = Self.versionNumber(for: provider, store: store) { return \"\\(meta.displayName) \\(ver)\" }\n            return meta.displayName\n        }()\n        entries.append(.text(headlineText, .headline))\n\n        if let snap = store.snapshot(for: provider) {\n            let resetStyle = settings.resetTimeDisplayStyle\n            if let primary = snap.primary {\n                let primaryWindow = if provider == .warp || provider == .kilo {\n                    // Warp/Kilo primary uses resetDescription for non-reset detail (e.g., \"Unlimited\", \"X/Y credits\").\n                    // Avoid rendering it as a \"Resets ...\" line.\n                    RateWindow(\n                        usedPercent: primary.usedPercent,\n                        windowMinutes: primary.windowMinutes,\n                        resetsAt: primary.resetsAt,\n                        resetDescription: nil)\n                } else {\n                    primary\n                }\n                Self.appendRateWindow(\n                    entries: &entries,\n                    title: meta.sessionLabel,\n                    window: primaryWindow,\n                    resetStyle: resetStyle,\n                    showUsed: settings.usageBarsShowUsed)\n                if provider == .warp || provider == .kilo,\n                   let detail = primary.resetDescription?.trimmingCharacters(in: .whitespacesAndNewlines),\n                   !detail.isEmpty\n                {\n                    entries.append(.text(detail, .secondary))\n                }\n            }\n            if let weekly = snap.secondary {\n                let weeklyResetOverride: String? = {\n                    guard provider == .warp || provider == .kilo else { return nil }\n                    let detail = weekly.resetDescription?.trimmingCharacters(in: .whitespacesAndNewlines)\n                    guard let detail, !detail.isEmpty else { return nil }\n                    if provider == .kilo, weekly.resetsAt != nil {\n                        return nil\n                    }\n                    return detail\n                }()\n                Self.appendRateWindow(\n                    entries: &entries,\n                    title: meta.weeklyLabel,\n                    window: weekly,\n                    resetStyle: resetStyle,\n                    showUsed: settings.usageBarsShowUsed,\n                    resetOverride: weeklyResetOverride)\n                if provider == .kilo,\n                   weekly.resetsAt != nil,\n                   let detail = weekly.resetDescription?.trimmingCharacters(in: .whitespacesAndNewlines),\n                   !detail.isEmpty\n                {\n                    entries.append(.text(detail, .secondary))\n                }\n                if let pace = store.weeklyPace(provider: provider, window: weekly) {\n                    let paceSummary = UsagePaceText.weeklySummary(pace: pace)\n                    entries.append(.text(paceSummary, .secondary))\n                }\n            }\n            if meta.supportsOpus, let opus = snap.tertiary {\n                Self.appendRateWindow(\n                    entries: &entries,\n                    title: meta.opusLabel ?? \"Sonnet\",\n                    window: opus,\n                    resetStyle: resetStyle,\n                    showUsed: settings.usageBarsShowUsed)\n            }\n\n            if let cost = snap.providerCost {\n                if cost.currencyCode == \"Quota\" {\n                    let used = String(format: \"%.0f\", cost.used)\n                    let limit = String(format: \"%.0f\", cost.limit)\n                    entries.append(.text(\"Quota: \\(used) / \\(limit)\", .primary))\n                }\n            }\n        } else {\n            entries.append(.text(\"No usage yet\", .secondary))\n        }\n\n        let usageContext = ProviderMenuUsageContext(\n            provider: provider,\n            store: store,\n            settings: settings,\n            metadata: meta,\n            snapshot: store.snapshot(for: provider))\n        ProviderCatalog.implementation(for: provider)?\n            .appendUsageMenuEntries(context: usageContext, entries: &entries)\n\n        return Section(entries: entries)\n    }\n\n    private static func accountSection(\n        for provider: UsageProvider,\n        store: UsageStore,\n        settings: SettingsStore,\n        account: AccountInfo) -> Section?\n    {\n        let snapshot = store.snapshot(for: provider)\n        let metadata = store.metadata(for: provider)\n        let entries = Self.accountEntries(\n            provider: provider,\n            snapshot: snapshot,\n            metadata: metadata,\n            fallback: account,\n            hidePersonalInfo: settings.hidePersonalInfo)\n        guard !entries.isEmpty else { return nil }\n        return Section(entries: entries)\n    }\n\n    private static func accountEntries(\n        provider: UsageProvider,\n        snapshot: UsageSnapshot?,\n        metadata: ProviderMetadata,\n        fallback: AccountInfo,\n        hidePersonalInfo: Bool) -> [Entry]\n    {\n        var entries: [Entry] = []\n        let emailText = snapshot?.accountEmail(for: provider)?\n            .trimmingCharacters(in: .whitespacesAndNewlines)\n        let loginMethodText = snapshot?.loginMethod(for: provider)?\n            .trimmingCharacters(in: .whitespacesAndNewlines)\n        let redactedEmail = PersonalInfoRedactor.redactEmail(emailText, isEnabled: hidePersonalInfo)\n\n        if let emailText, !emailText.isEmpty {\n            entries.append(.text(\"Account: \\(redactedEmail)\", .secondary))\n        }\n        if provider == .kilo {\n            let kiloLogin = self.kiloLoginParts(loginMethod: loginMethodText)\n            if let pass = kiloLogin.pass {\n                entries.append(.text(\"Plan: \\(AccountFormatter.plan(pass))\", .secondary))\n            }\n            for detail in kiloLogin.details {\n                entries.append(.text(\"Activity: \\(detail)\", .secondary))\n            }\n        } else if let loginMethodText, !loginMethodText.isEmpty {\n            entries.append(.text(\"Plan: \\(AccountFormatter.plan(loginMethodText))\", .secondary))\n        }\n\n        if metadata.usesAccountFallback {\n            if emailText?.isEmpty ?? true, let fallbackEmail = fallback.email, !fallbackEmail.isEmpty {\n                let redacted = PersonalInfoRedactor.redactEmail(fallbackEmail, isEnabled: hidePersonalInfo)\n                entries.append(.text(\"Account: \\(redacted)\", .secondary))\n            }\n            if loginMethodText?.isEmpty ?? true, let fallbackPlan = fallback.plan, !fallbackPlan.isEmpty {\n                entries.append(.text(\"Plan: \\(AccountFormatter.plan(fallbackPlan))\", .secondary))\n            }\n        }\n\n        return entries\n    }\n\n    private static func kiloLoginParts(loginMethod: String?) -> (pass: String?, details: [String]) {\n        guard let loginMethod else {\n            return (nil, [])\n        }\n        let parts = loginMethod\n            .components(separatedBy: \"·\")\n            .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }\n            .filter { !$0.isEmpty }\n        guard !parts.isEmpty else {\n            return (nil, [])\n        }\n        let first = parts[0]\n        if self.isKiloActivitySegment(first) {\n            return (nil, parts)\n        }\n        return (first, Array(parts.dropFirst()))\n    }\n\n    private static func isKiloActivitySegment(_ text: String) -> Bool {\n        let normalized = text.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()\n        return normalized.hasPrefix(\"auto top-up:\")\n    }\n\n    private static func accountProviderForCombined(store: UsageStore) -> UsageProvider? {\n        for provider in store.enabledProviders() {\n            let metadata = store.metadata(for: provider)\n            if store.snapshot(for: provider)?.identity(for: provider) != nil {\n                return provider\n            }\n            if metadata.usesAccountFallback {\n                return provider\n            }\n        }\n        return nil\n    }\n\n    private static func actionsSection(\n        for provider: UsageProvider?,\n        store: UsageStore,\n        account: AccountInfo) -> Section\n    {\n        var entries: [Entry] = []\n        let targetProvider = provider ?? store.enabledProviders().first\n        let metadata = targetProvider.map { store.metadata(for: $0) }\n        let loginContext = targetProvider.map {\n            ProviderMenuLoginContext(\n                provider: $0,\n                store: store,\n                settings: store.settings,\n                account: account)\n        }\n\n        // Show \"Add Account\" if no account, \"Switch Account\" if logged in\n        if let targetProvider,\n           let implementation = ProviderCatalog.implementation(for: targetProvider),\n           implementation.supportsLoginFlow\n        {\n            if let loginContext,\n               let override = implementation.loginMenuAction(context: loginContext)\n            {\n                entries.append(.action(override.label, override.action))\n            } else {\n                let loginAction = self.switchAccountTarget(for: provider, store: store)\n                let hasAccount = self.hasAccount(for: provider, store: store, account: account)\n                let accountLabel = hasAccount ? \"Switch Account...\" : \"Add Account...\"\n                entries.append(.action(accountLabel, loginAction))\n            }\n        }\n\n        if let targetProvider {\n            let actionContext = ProviderMenuActionContext(\n                provider: targetProvider,\n                store: store,\n                settings: store.settings,\n                account: account)\n            ProviderCatalog.implementation(for: targetProvider)?\n                .appendActionMenuEntries(context: actionContext, entries: &entries)\n        }\n\n        if metadata?.dashboardURL != nil {\n            entries.append(.action(\"Usage Dashboard\", .dashboard))\n        }\n        if metadata?.statusPageURL != nil || metadata?.statusLinkURL != nil {\n            entries.append(.action(\"Status Page\", .statusPage))\n        }\n\n        if let statusLine = self.statusLine(for: provider, store: store) {\n            entries.append(.text(statusLine, .secondary))\n        }\n\n        return Section(entries: entries)\n    }\n\n    private static func metaSection(updateReady: Bool) -> Section {\n        var entries: [Entry] = []\n        if updateReady {\n            entries.append(.action(\"Update ready, restart now?\", .installUpdate))\n        }\n        entries.append(contentsOf: [\n            .action(\"Settings...\", .settings),\n            .action(\"About CodexBar\", .about),\n            .action(\"Quit\", .quit),\n        ])\n        return Section(entries: entries)\n    }\n\n    private static func statusLine(for provider: UsageProvider?, store: UsageStore) -> String? {\n        let target = provider ?? store.enabledProviders().first\n        guard let target,\n              let status = store.status(for: target),\n              status.indicator != .none else { return nil }\n\n        let description = status.description?.trimmingCharacters(in: .whitespacesAndNewlines)\n        let label = description?.isEmpty == false ? description! : status.indicator.label\n        if let updated = status.updatedAt {\n            let freshness = UsageFormatter.updatedString(from: updated)\n            return \"\\(label) — \\(freshness)\"\n        }\n        return label\n    }\n\n    private static func switchAccountTarget(for provider: UsageProvider?, store: UsageStore) -> MenuAction {\n        if let provider { return .switchAccount(provider) }\n        if let enabled = store.enabledProviders().first { return .switchAccount(enabled) }\n        return .switchAccount(.codex)\n    }\n\n    private static func hasAccount(for provider: UsageProvider?, store: UsageStore, account: AccountInfo) -> Bool {\n        let target = provider ?? store.enabledProviders().first ?? .codex\n        if let email = store.snapshot(for: target)?.accountEmail(for: target),\n           !email.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty\n        {\n            return true\n        }\n        let metadata = store.metadata(for: target)\n        if metadata.usesAccountFallback,\n           let fallback = account.email?.trimmingCharacters(in: .whitespacesAndNewlines),\n           !fallback.isEmpty\n        {\n            return true\n        }\n        return false\n    }\n\n    private static func appendRateWindow(\n        entries: inout [Entry],\n        title: String,\n        window: RateWindow,\n        resetStyle: ResetTimeDisplayStyle,\n        showUsed: Bool,\n        resetOverride: String? = nil)\n    {\n        let line = UsageFormatter\n            .usageLine(remaining: window.remainingPercent, used: window.usedPercent, showUsed: showUsed)\n        entries.append(.text(\"\\(title): \\(line)\", .primary))\n        if let resetOverride {\n            entries.append(.text(resetOverride, .secondary))\n        } else if let reset = UsageFormatter.resetLine(for: window, style: resetStyle) {\n            entries.append(.text(reset, .secondary))\n        }\n    }\n\n    private static func versionNumber(for provider: UsageProvider, store: UsageStore) -> String? {\n        guard let raw = store.version(for: provider) else { return nil }\n        let pattern = #\"[0-9]+(?:\\.[0-9]+)*\"#\n        guard let regex = try? NSRegularExpression(pattern: pattern) else { return nil }\n        let range = NSRange(raw.startIndex..<raw.endIndex, in: raw)\n        guard let match = regex.firstMatch(in: raw, options: [], range: range),\n              let r = Range(match.range, in: raw) else { return nil }\n        return String(raw[r])\n    }\n}\n\nprivate enum AccountFormatter {\n    static func plan(_ text: String) -> String {\n        let cleaned = UsageFormatter.cleanPlanName(text)\n        return cleaned.isEmpty ? text : cleaned\n    }\n\n    static func email(_ text: String) -> String {\n        text\n    }\n}\n\nextension MenuDescriptor.MenuAction {\n    var systemImageName: String? {\n        switch self {\n        case .installUpdate, .settings, .about, .quit:\n            nil\n        case .refresh: MenuDescriptor.MenuActionSystemImage.refresh.rawValue\n        case .refreshAugmentSession: MenuDescriptor.MenuActionSystemImage.refresh.rawValue\n        case .dashboard: MenuDescriptor.MenuActionSystemImage.dashboard.rawValue\n        case .statusPage: MenuDescriptor.MenuActionSystemImage.statusPage.rawValue\n        case .switchAccount: MenuDescriptor.MenuActionSystemImage.switchAccount.rawValue\n        case .openTerminal: MenuDescriptor.MenuActionSystemImage.openTerminal.rawValue\n        case .loginToProvider: MenuDescriptor.MenuActionSystemImage.loginToProvider.rawValue\n        case .copyError: MenuDescriptor.MenuActionSystemImage.copyError.rawValue\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/MenuHighlightStyle.swift",
    "content": "import SwiftUI\n\nextension EnvironmentValues {\n    @Entry var menuItemHighlighted: Bool = false\n}\n\nenum MenuHighlightStyle {\n    static let selectionText = Color(nsColor: .selectedMenuItemTextColor)\n    static let normalPrimaryText = Color(nsColor: .controlTextColor)\n    static let normalSecondaryText = Color(nsColor: .secondaryLabelColor)\n\n    static func primary(_ highlighted: Bool) -> Color {\n        highlighted ? self.selectionText : self.normalPrimaryText\n    }\n\n    static func secondary(_ highlighted: Bool) -> Color {\n        highlighted ? self.selectionText : self.normalSecondaryText\n    }\n\n    static func error(_ highlighted: Bool) -> Color {\n        highlighted ? self.selectionText : Color(nsColor: .systemRed)\n    }\n\n    static func progressTrack(_ highlighted: Bool) -> Color {\n        highlighted ? self.selectionText.opacity(0.22) : Color(nsColor: .tertiaryLabelColor).opacity(0.22)\n    }\n\n    static func progressTint(_ highlighted: Bool, fallback: Color) -> Color {\n        highlighted ? self.selectionText : fallback\n    }\n\n    static func selectionBackground(_ highlighted: Bool) -> Color {\n        highlighted ? Color(nsColor: .selectedContentBackgroundColor) : .clear\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/MiniMaxAPITokenStore.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Security\n\nprotocol MiniMaxAPITokenStoring: Sendable {\n    func loadToken() throws -> String?\n    func storeToken(_ token: String?) throws\n}\n\nenum MiniMaxAPITokenStoreError: LocalizedError {\n    case keychainStatus(OSStatus)\n    case invalidData\n\n    var errorDescription: String? {\n        switch self {\n        case let .keychainStatus(status):\n            \"Keychain error: \\(status)\"\n        case .invalidData:\n            \"Keychain returned invalid data.\"\n        }\n    }\n}\n\nstruct KeychainMiniMaxAPITokenStore: MiniMaxAPITokenStoring {\n    private static let log = CodexBarLog.logger(LogCategories.minimaxAPITokenStore)\n\n    private let service = \"com.steipete.CodexBar\"\n    private let account = \"minimax-api-token\"\n\n    func loadToken() throws -> String? {\n        guard !KeychainAccessGate.isDisabled else {\n            Self.log.debug(\"Keychain access disabled; skipping token load\")\n            return nil\n        }\n        var result: CFTypeRef?\n        let query: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: self.service,\n            kSecAttrAccount as String: self.account,\n            kSecMatchLimit as String: kSecMatchLimitOne,\n            kSecReturnData as String: true,\n        ]\n\n        if case .interactionRequired = KeychainAccessPreflight\n            .checkGenericPassword(service: self.service, account: self.account)\n        {\n            KeychainPromptHandler.handler?(KeychainPromptContext(\n                kind: .minimaxToken,\n                service: self.service,\n                account: self.account))\n        }\n\n        let status = SecItemCopyMatching(query as CFDictionary, &result)\n        if status == errSecItemNotFound {\n            return nil\n        }\n        guard status == errSecSuccess else {\n            Self.log.error(\"Keychain read failed: \\(status)\")\n            throw MiniMaxAPITokenStoreError.keychainStatus(status)\n        }\n\n        guard let data = result as? Data else {\n            throw MiniMaxAPITokenStoreError.invalidData\n        }\n        let token = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines)\n        if let token, !token.isEmpty {\n            return token\n        }\n        return nil\n    }\n\n    func storeToken(_ token: String?) throws {\n        guard !KeychainAccessGate.isDisabled else {\n            Self.log.debug(\"Keychain access disabled; skipping token store\")\n            return\n        }\n        let cleaned = token?.trimmingCharacters(in: .whitespacesAndNewlines)\n        if cleaned == nil || cleaned?.isEmpty == true {\n            try self.deleteIfPresent()\n            return\n        }\n\n        let data = cleaned!.data(using: .utf8)!\n        let query: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: self.service,\n            kSecAttrAccount as String: self.account,\n        ]\n        let attributes: [String: Any] = [\n            kSecValueData as String: data,\n            kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,\n        ]\n\n        let updateStatus = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)\n        if updateStatus == errSecSuccess {\n            return\n        }\n        if updateStatus != errSecItemNotFound {\n            Self.log.error(\"Keychain update failed: \\(updateStatus)\")\n            throw MiniMaxAPITokenStoreError.keychainStatus(updateStatus)\n        }\n\n        var addQuery = query\n        for (key, value) in attributes {\n            addQuery[key] = value\n        }\n        let addStatus = SecItemAdd(addQuery as CFDictionary, nil)\n        guard addStatus == errSecSuccess else {\n            Self.log.error(\"Keychain add failed: \\(addStatus)\")\n            throw MiniMaxAPITokenStoreError.keychainStatus(addStatus)\n        }\n    }\n\n    private func deleteIfPresent() throws {\n        guard !KeychainAccessGate.isDisabled else { return }\n        let query: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: self.service,\n            kSecAttrAccount as String: self.account,\n        ]\n        let status = SecItemDelete(query as CFDictionary)\n        if status == errSecSuccess || status == errSecItemNotFound {\n            return\n        }\n        Self.log.error(\"Keychain delete failed: \\(status)\")\n        throw MiniMaxAPITokenStoreError.keychainStatus(status)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/MiniMaxCookieStore.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Security\n\nprotocol MiniMaxCookieStoring: Sendable {\n    func loadCookieHeader() throws -> String?\n    func storeCookieHeader(_ header: String?) throws\n}\n\nenum MiniMaxCookieStoreError: LocalizedError {\n    case keychainStatus(OSStatus)\n    case invalidData\n\n    var errorDescription: String? {\n        switch self {\n        case let .keychainStatus(status):\n            \"Keychain error: \\(status)\"\n        case .invalidData:\n            \"Keychain returned invalid data.\"\n        }\n    }\n}\n\nstruct KeychainMiniMaxCookieStore: MiniMaxCookieStoring {\n    private static let log = CodexBarLog.logger(LogCategories.minimaxCookieStore)\n\n    private let service = \"com.steipete.CodexBar\"\n    private let account = \"minimax-cookie\"\n\n    func loadCookieHeader() throws -> String? {\n        guard !KeychainAccessGate.isDisabled else {\n            Self.log.debug(\"Keychain access disabled; skipping cookie load\")\n            return nil\n        }\n        var result: CFTypeRef?\n        let query: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: self.service,\n            kSecAttrAccount as String: self.account,\n            kSecMatchLimit as String: kSecMatchLimitOne,\n            kSecReturnData as String: true,\n        ]\n\n        if case .interactionRequired = KeychainAccessPreflight\n            .checkGenericPassword(service: self.service, account: self.account)\n        {\n            KeychainPromptHandler.handler?(KeychainPromptContext(\n                kind: .minimaxCookie,\n                service: self.service,\n                account: self.account))\n        }\n\n        let status = SecItemCopyMatching(query as CFDictionary, &result)\n        if status == errSecItemNotFound {\n            return nil\n        }\n        guard status == errSecSuccess else {\n            Self.log.error(\"Keychain read failed: \\(status)\")\n            throw MiniMaxCookieStoreError.keychainStatus(status)\n        }\n\n        guard let data = result as? Data else {\n            throw MiniMaxCookieStoreError.invalidData\n        }\n        let header = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines)\n        if let header, !header.isEmpty {\n            return header\n        }\n        return nil\n    }\n\n    func storeCookieHeader(_ header: String?) throws {\n        guard !KeychainAccessGate.isDisabled else {\n            Self.log.debug(\"Keychain access disabled; skipping cookie store\")\n            return\n        }\n        guard let raw = header?.trimmingCharacters(in: .whitespacesAndNewlines),\n              !raw.isEmpty\n        else {\n            try self.deleteIfPresent()\n            return\n        }\n        guard MiniMaxCookieHeader.normalized(from: raw) != nil else {\n            try self.deleteIfPresent()\n            return\n        }\n\n        let data = raw.data(using: .utf8)!\n        let query: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: self.service,\n            kSecAttrAccount as String: self.account,\n        ]\n        let attributes: [String: Any] = [\n            kSecValueData as String: data,\n            kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,\n        ]\n\n        let updateStatus = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)\n        if updateStatus == errSecSuccess {\n            return\n        }\n        if updateStatus != errSecItemNotFound {\n            Self.log.error(\"Keychain update failed: \\(updateStatus)\")\n            throw MiniMaxCookieStoreError.keychainStatus(updateStatus)\n        }\n\n        var addQuery = query\n        for (key, value) in attributes {\n            addQuery[key] = value\n        }\n        let addStatus = SecItemAdd(addQuery as CFDictionary, nil)\n        guard addStatus == errSecSuccess else {\n            Self.log.error(\"Keychain add failed: \\(addStatus)\")\n            throw MiniMaxCookieStoreError.keychainStatus(addStatus)\n        }\n    }\n\n    private func deleteIfPresent() throws {\n        guard !KeychainAccessGate.isDisabled else { return }\n        let query: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: self.service,\n            kSecAttrAccount as String: self.account,\n        ]\n        let status = SecItemDelete(query as CFDictionary)\n        if status == errSecSuccess || status == errSecItemNotFound {\n            return\n        }\n        Self.log.error(\"Keychain delete failed: \\(status)\")\n        throw MiniMaxCookieStoreError.keychainStatus(status)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/MouseLocationReader.swift",
    "content": "import AppKit\nimport SwiftUI\n\n/// Lightweight NSView-based mouse tracking with local coordinates.\n///\n/// Why: SwiftUI's `onHover` doesn't provide location, but we want \"hover a bar to see values\" on macOS.\n@MainActor\nstruct MouseLocationReader: NSViewRepresentable {\n    let onMoved: (CGPoint?) -> Void\n\n    func makeNSView(context: Context) -> TrackingView {\n        let view = TrackingView()\n        view.onMoved = self.onMoved\n        return view\n    }\n\n    func updateNSView(_ nsView: TrackingView, context: Context) {\n        nsView.onMoved = self.onMoved\n    }\n\n    final class TrackingView: NSView {\n        var onMoved: ((CGPoint?) -> Void)?\n        private var trackingArea: NSTrackingArea?\n\n        override func viewDidMoveToWindow() {\n            super.viewDidMoveToWindow()\n            self.window?.acceptsMouseMovedEvents = true\n            self.updateTrackingAreas()\n        }\n\n        override func updateTrackingAreas() {\n            super.updateTrackingAreas()\n            if let trackingArea {\n                self.removeTrackingArea(trackingArea)\n            }\n\n            let options: NSTrackingArea.Options = [\n                // NSMenu popups aren't \"key windows\", so `.activeInKeyWindow` would drop events and cause hover\n                // state to flicker. `.activeAlways` keeps tracking stable while the menu is open.\n                .activeAlways,\n                .inVisibleRect,\n                .mouseEnteredAndExited,\n                .mouseMoved,\n            ]\n            let area = NSTrackingArea(rect: .zero, options: options, owner: self, userInfo: nil)\n            self.addTrackingArea(area)\n            self.trackingArea = area\n        }\n\n        override func mouseEntered(with event: NSEvent) {\n            super.mouseEntered(with: event)\n            self.onMoved?(self.convert(event.locationInWindow, from: nil))\n        }\n\n        override func mouseMoved(with event: NSEvent) {\n            super.mouseMoved(with: event)\n            self.onMoved?(self.convert(event.locationInWindow, from: nil))\n        }\n\n        override func mouseExited(with event: NSEvent) {\n            super.mouseExited(with: event)\n            self.onMoved?(nil)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Notifications+CodexBar.swift",
    "content": "import Foundation\n\nextension Notification.Name {\n    static let codexbarOpenSettings = Notification.Name(\"codexbarOpenSettings\")\n    static let codexbarDebugBlinkNow = Notification.Name(\"codexbarDebugBlinkNow\")\n    static let codexbarProviderConfigDidChange = Notification.Name(\"codexbarProviderConfigDidChange\")\n}\n"
  },
  {
    "path": "Sources/CodexBar/OpenAICreditsPurchaseWindowController.swift",
    "content": "import AppKit\nimport CodexBarCore\nimport WebKit\n\n@MainActor\nfinal class OpenAICreditsPurchaseWindowController: NSWindowController, WKNavigationDelegate, WKScriptMessageHandler {\n    private static let defaultSize = NSSize(width: 980, height: 760)\n    private static let logHandlerName = \"codexbarLog\"\n    private static let debugLogURL = URL(fileURLWithPath: NSTemporaryDirectory())\n        .appendingPathComponent(\"codexbar-buy-credits.log\")\n    private static let autoStartScript = \"\"\"\n    (() => {\n      if (window.__codexbarAutoBuyCreditsStarted) return 'already';\n      const log = (...args) => {\n        try {\n          window.webkit?.messageHandlers?.codexbarLog?.postMessage(args);\n        } catch {}\n      };\n      const buttonSelector = 'button, a, [role=\"button\"], input[type=\"button\"], input[type=\"submit\"]';\n      const isVisible = (el) => {\n        if (!el || !el.getBoundingClientRect) return false;\n        const rect = el.getBoundingClientRect();\n        if (rect.width < 2 || rect.height < 2) return false;\n        const style = window.getComputedStyle ? window.getComputedStyle(el) : null;\n        if (style) {\n          if (style.display === 'none' || style.visibility === 'hidden') return false;\n          if (parseFloat(style.opacity || '1') === 0) return false;\n        }\n        return true;\n      };\n      const textOf = el => {\n        const raw = el && (el.innerText || el.textContent) ? String(el.innerText || el.textContent) : '';\n        return raw.trim();\n      };\n      const matches = text => {\n        const lower = String(text || '').toLowerCase();\n        if (!lower.includes('credit')) return false;\n        return (\n          lower.includes('buy') ||\n          lower.includes('add') ||\n          lower.includes('purchase') ||\n          lower.includes('top up') ||\n          lower.includes('top-up')\n        );\n      };\n      const matchesAddMore = text => {\n        const lower = String(text || '').toLowerCase();\n        return lower.includes('add more');\n      };\n      const labelFor = el => {\n        if (!el) return '';\n        return textOf(el) || el.getAttribute('aria-label') || el.getAttribute('title') || el.value || '';\n      };\n      const summarize = el => {\n        if (!el) return null;\n        return {\n          tag: el.tagName,\n          type: el.getAttribute('type'),\n          role: el.getAttribute('role'),\n          label: labelFor(el),\n          aria: el.getAttribute('aria-label'),\n          disabled: isDisabled(el),\n          href: el.getAttribute('href'),\n          testId: el.getAttribute('data-testid'),\n          className: (el.className && String(el.className).slice(0, 120)) || ''\n        };\n      };\n      const collectButtons = () => {\n        const results = new Set();\n        const addAll = (root) => {\n          if (!root || !root.querySelectorAll) return;\n          root.querySelectorAll(buttonSelector).forEach(el => results.add(el));\n        };\n        addAll(document);\n        document.querySelectorAll('*').forEach(el => {\n          if (el.shadowRoot) addAll(el.shadowRoot);\n        });\n        document.querySelectorAll('iframe').forEach(frame => {\n          try {\n            const doc = frame.contentDocument;\n            if (!doc) return;\n            addAll(doc);\n            doc.querySelectorAll('*').forEach(el => {\n              if (el.shadowRoot) addAll(el.shadowRoot);\n            });\n          } catch {}\n        });\n        return Array.from(results);\n      };\n      const findDialogNextButton = () => {\n        const dialog = document.querySelector('[role=\\\"dialog\\\"], dialog, [aria-modal=\\\"true\\\"]');\n        if (!dialog) return null;\n        const buttons = Array.from(dialog.querySelectorAll(buttonSelector));\n        const labeled = buttons.filter(btn => labelFor(btn).toLowerCase().startsWith('next'));\n        const visible = labeled.find(isVisible);\n        return visible || labeled[0] || null;\n      };\n      const clickButton = (el) => {\n        if (!el) return false;\n        try {\n          el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }));\n        } catch {\n          try {\n            el.click();\n          } catch {\n            return false;\n          }\n        }\n        return true;\n      };\n      const triggerPointerClick = (el) => {\n        if (!el) return false;\n        const rect = el.getBoundingClientRect ? el.getBoundingClientRect() : null;\n        if (!rect) return false;\n        const x = rect.left + rect.width / 2;\n        const y = rect.top + rect.height / 2;\n        const events = ['pointerdown', 'mousedown', 'pointerup', 'mouseup', 'click'];\n        for (const type of events) {\n          try {\n            el.dispatchEvent(new MouseEvent(type, {\n              bubbles: true,\n              cancelable: true,\n              view: window,\n              clientX: x,\n              clientY: y\n            }));\n          } catch {\n            return false;\n          }\n        }\n        return true;\n      };\n      const pickLikelyButton = (buttons) => {\n        if (!buttons || buttons.length === 0) return null;\n        const labeled = buttons.find(btn => {\n          const label = labelFor(btn);\n          if (matches(label) || matchesAddMore(label)) return true;\n          const aria = String(btn.getAttribute('aria-label') || '').toLowerCase();\n          return aria.includes('credit') || aria.includes('buy') || aria.includes('add');\n        });\n        return labeled || buttons[0];\n      };\n      const findAddMoreButton = () => {\n        const buttons = collectButtons();\n        return buttons.find(btn => matchesAddMore(labelFor(btn))) || null;\n      };\n      const findNextButton = () => {\n        const dialogNext = findDialogNextButton();\n        if (dialogNext) return dialogNext;\n        const buttons = collectButtons();\n        const labeled = buttons.filter(btn => {\n          const label = labelFor(btn).toLowerCase();\n          return label === 'next' || label.startsWith('next ');\n        });\n        const visible = labeled.find(isVisible);\n        if (visible) return visible;\n        const submit = buttons.find(btn => btn.type && String(btn.type).toLowerCase() === 'submit' && isVisible(btn));\n        return submit || labeled[0] || null;\n      };\n      const isDisabled = (el) => {\n        if (!el) return true;\n        if (el.disabled) return true;\n        const ariaDisabled = String(el.getAttribute('aria-disabled') || '').toLowerCase();\n        if (ariaDisabled === 'true') return true;\n        if (el.classList && (el.classList.contains('disabled') || el.classList.contains('is-disabled'))) {\n          return true;\n        }\n        return false;\n      };\n      const forceClickElement = (el) => {\n        if (!el) return false;\n        const rect = el.getBoundingClientRect ? el.getBoundingClientRect() : null;\n        if (rect) {\n          const x = rect.left + rect.width / 2;\n          const y = rect.top + rect.height / 2;\n          const target = document.elementFromPoint(x, y);\n          if (target) {\n            try {\n              target.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }));\n              return true;\n            } catch {\n              return false;\n            }\n          }\n        }\n        return false;\n      };\n      const requestSubmit = (el) => {\n        if (!el || !el.closest) return false;\n        const form = el.closest('form');\n        if (!form) return false;\n        if (typeof form.requestSubmit === 'function') {\n          form.requestSubmit(el);\n          return true;\n        }\n        if (typeof form.submit === 'function') {\n          form.submit();\n          return true;\n        }\n        return false;\n      };\n      const clickNextIfReady = (attempts) => {\n        const nextButton = findNextButton();\n        if (!nextButton) {\n          if (attempts && attempts % 5 === 0) log('next_missing', { attempts });\n          return false;\n        }\n        if (isDisabled(nextButton)) {\n          if (attempts && attempts % 5 === 0) log('next_disabled', summarize(nextButton));\n          return false;\n        }\n        if (!isVisible(nextButton)) {\n          if (attempts && attempts % 5 === 0) log('next_hidden', summarize(nextButton));\n          return false;\n        }\n        nextButton.focus?.();\n        if (requestSubmit(nextButton)) {\n          log('next_submit', summarize(nextButton));\n          return true;\n        }\n        if (triggerPointerClick(nextButton)) {\n          log('next_pointer', summarize(nextButton));\n          return true;\n        }\n        if (clickButton(nextButton)) {\n          log('next_click', summarize(nextButton));\n          return true;\n        }\n        return forceClickElement(nextButton);\n      };\n      const startNextPolling = (initialDelay = 500, interval = 500, maxAttempts = 90) => {\n        if (window.__codexbarNextPolling) return;\n        window.__codexbarNextPolling = true;\n        log('start_next_poll', { initialDelay, interval, maxAttempts });\n        setTimeout(() => {\n          let attempts = 0;\n          const nextTimer = setInterval(() => {\n            attempts += 1;\n            if (attempts % 5 === 0) {\n              const nextButton = findNextButton();\n              log('next_poll', {\n                attempts,\n                found: Boolean(nextButton),\n                summary: summarize(nextButton)\n              });\n            }\n            if (clickNextIfReady(attempts) || attempts >= maxAttempts) {\n              clearInterval(nextTimer);\n            }\n          }, interval);\n        }, initialDelay);\n      };\n      const observeNextButton = () => {\n        if (window.__codexbarNextObserver || !window.MutationObserver) return;\n        const observer = new MutationObserver(() => {\n          if (clickNextIfReady(1)) {\n            observer.disconnect();\n            window.__codexbarNextObserver = null;\n          }\n        });\n        observer.observe(document.body, { subtree: true, childList: true, attributes: true });\n        window.__codexbarNextObserver = observer;\n      };\n      const findCreditsCardButton = () => {\n        const nodes = Array.from(document.querySelectorAll('h1,h2,h3,div,span,p'));\n        const labelMatch = nodes.find(node => {\n          const lower = textOf(node).toLowerCase();\n          return lower === 'credits remaining' || (lower.includes('credits') && lower.includes('remaining'));\n        });\n        if (!labelMatch) return null;\n        let cur = labelMatch;\n        for (let i = 0; i < 6 && cur; i++) {\n          const buttons = Array.from(cur.querySelectorAll(buttonSelector));\n          const picked = pickLikelyButton(buttons);\n          if (picked) return picked;\n          cur = cur.parentElement;\n        }\n        return null;\n      };\n      const findAndClick = () => {\n        const addMoreButton = findAddMoreButton();\n        if (addMoreButton) {\n          log('add_more_click', summarize(addMoreButton));\n          clickButton(addMoreButton);\n          return true;\n        }\n        const cardButton = findCreditsCardButton();\n        if (!cardButton) return false;\n        log('credits_card_click', summarize(cardButton));\n        return clickButton(cardButton);\n      };\n      const logDialogButtons = () => {\n        const dialog = document.querySelector('[role=\\\"dialog\\\"], dialog, [aria-modal=\\\"true\\\"]');\n        if (dialog) {\n          const buttons = Array.from(dialog.querySelectorAll(buttonSelector)).map(summarize).filter(Boolean);\n          if (buttons.length) {\n            log('dialog_buttons', { count: buttons.length, buttons: buttons.slice(0, 6) });\n          }\n          const nextButton = findDialogNextButton();\n          if (nextButton) {\n            log('dialog_next', summarize(nextButton));\n            setTimeout(() => clickNextIfReady(1), 100);\n          }\n          return;\n        }\n        const candidates = collectButtons()\n          .map(summarize)\n          .filter(Boolean)\n          .filter(entry => {\n            const label = (entry.label || '').toLowerCase();\n            return label.includes('next')\n              || label.includes('continue')\n              || label.includes('confirm')\n              || label.includes('buy');\n          });\n        if (candidates.length) {\n          log('button_candidates', { count: candidates.length, buttons: candidates.slice(0, 8) });\n        }\n      };\n      log('auto_start', { href: location.href, ready: document.readyState });\n      const iframeSources = Array.from(document.querySelectorAll('iframe'))\n        .map(frame => frame.getAttribute('src') || '')\n        .filter(Boolean)\n        .slice(0, 6);\n      if (iframeSources.length) {\n        log('iframes', iframeSources);\n      }\n      const shadowHostCount = Array.from(document.querySelectorAll('*')).filter(el => el.shadowRoot).length;\n      if (shadowHostCount > 0) {\n        log('shadow_roots', { count: shadowHostCount });\n      }\n      if (findAndClick()) {\n        window.__codexbarAutoBuyCreditsStarted = true;\n        startNextPolling();\n        observeNextButton();\n        logDialogButtons();\n        return 'clicked';\n      }\n      startNextPolling(500);\n      observeNextButton();\n      logDialogButtons();\n      let attempts = 0;\n      const maxAttempts = 14;\n      const timer = setInterval(() => {\n        attempts += 1;\n        if (findAndClick()) {\n          logDialogButtons();\n          startNextPolling();\n          clearInterval(timer);\n          return;\n        }\n        if (attempts >= maxAttempts) {\n          clearInterval(timer);\n        }\n      }, 500);\n      window.__codexbarAutoBuyCreditsStarted = true;\n      return 'scheduled';\n    })();\n    \"\"\"\n\n    private let logger = CodexBarLog.logger(LogCategories.creditsPurchase)\n    private var webView: WKWebView?\n    private var accountEmail: String?\n    private var pendingAutoStart = false\n    private let logHandler = WeakScriptMessageHandler()\n\n    init() {\n        super.init(window: nil)\n        self.logHandler.delegate = self\n    }\n\n    @available(*, unavailable)\n    required init?(coder: NSCoder) {\n        fatalError(\"init(coder:) has not been implemented\")\n    }\n\n    func show(purchaseURL: URL, accountEmail: String?, autoStartPurchase: Bool) {\n        let normalizedEmail = Self.normalizeEmail(accountEmail)\n        if self.window == nil || normalizedEmail != self.accountEmail {\n            self.accountEmail = normalizedEmail\n            self.buildWindow()\n        }\n        Self.resetDebugLog()\n        let accountValue = normalizedEmail == nil ? \"none\" : \"set\"\n        let sanitizedURL = Self.sanitizedURLString(purchaseURL)\n        Self.appendDebugLog(\n            \"show autoStart=\\(autoStartPurchase) url=\\(sanitizedURL) account=\\(accountValue)\")\n        self.logger.info(\"Buy credits window opened\")\n        self.logger.debug(\"Auto-start purchase\", metadata: [\"enabled\": autoStartPurchase ? \"1\" : \"0\"])\n        self.logger.debug(\"Purchase URL\", metadata: [\"url\": sanitizedURL])\n        self.logger.debug(\"Account email\", metadata: [\"state\": accountValue])\n        self.pendingAutoStart = autoStartPurchase\n        self.load(url: purchaseURL)\n        self.window?.center()\n        self.showWindow(nil)\n        NSApp.activate(ignoringOtherApps: true)\n    }\n\n    private func buildWindow() {\n        let config = WKWebViewConfiguration()\n        config.userContentController.add(self.logHandler, name: Self.logHandlerName)\n        config.websiteDataStore = OpenAIDashboardWebsiteDataStore.store(forAccountEmail: self.accountEmail)\n\n        let webView = WKWebView(frame: .zero, configuration: config)\n        webView.navigationDelegate = self\n        webView.allowsBackForwardNavigationGestures = true\n        webView.translatesAutoresizingMaskIntoConstraints = false\n\n        let container = NSView(frame: .zero)\n        container.addSubview(webView)\n        NSLayoutConstraint.activate([\n            webView.leadingAnchor.constraint(equalTo: container.leadingAnchor),\n            webView.trailingAnchor.constraint(equalTo: container.trailingAnchor),\n            webView.topAnchor.constraint(equalTo: container.topAnchor),\n            webView.bottomAnchor.constraint(equalTo: container.bottomAnchor),\n        ])\n\n        let window = NSWindow(\n            contentRect: Self.defaultFrame(),\n            styleMask: [.titled, .closable, .resizable],\n            backing: .buffered,\n            defer: false)\n        window.title = \"Buy Credits\"\n        window.isReleasedWhenClosed = false\n        window.collectionBehavior = [.moveToActiveSpace, .fullScreenAuxiliary]\n        window.contentView = container\n        window.center()\n        window.delegate = self\n\n        self.window = window\n        self.webView = webView\n    }\n\n    private func load(url: URL) {\n        guard let webView else { return }\n        let request = URLRequest(url: url)\n        webView.load(request)\n    }\n\n    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {\n        guard self.pendingAutoStart else { return }\n        self.pendingAutoStart = false\n        let currentURL = webView.url?.absoluteString ?? \"unknown\"\n        Self.appendDebugLog(\"didFinish url=\\(currentURL)\")\n        self.logger.debug(\"Buy credits navigation finished\", metadata: [\"url\": currentURL])\n        webView.evaluateJavaScript(Self.autoStartScript) { [logger] result, error in\n            if let error {\n                Self.appendDebugLog(\"autoStart error=\\(error.localizedDescription)\")\n                logger.error(\"Auto-start purchase failed\", metadata: [\"error\": error.localizedDescription])\n                return\n            }\n            if let result {\n                Self.appendDebugLog(\"autoStart result=\\(String(describing: result))\")\n                logger.debug(\"Auto-start purchase result\", metadata: [\"result\": String(describing: result)])\n            }\n        }\n    }\n\n    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {\n        guard message.name == Self.logHandlerName else { return }\n        let payload = String(describing: message.body)\n        Self.appendDebugLog(\"js \\(payload)\")\n        self.logger.debug(\"Auto-buy log\", metadata: [\"payload\": payload])\n    }\n\n    private static func normalizeEmail(_ email: String?) -> String? {\n        guard let raw = email?.trimmingCharacters(in: .whitespacesAndNewlines), !raw.isEmpty else { return nil }\n        return raw.lowercased()\n    }\n\n    private static func defaultFrame() -> NSRect {\n        let visible = NSScreen.main?.visibleFrame ?? NSRect(x: 0, y: 0, width: 1200, height: 900)\n        let width = min(Self.defaultSize.width, visible.width * 0.92)\n        let height = min(Self.defaultSize.height, visible.height * 0.88)\n        let origin = NSPoint(x: visible.midX - width / 2, y: visible.midY - height / 2)\n        return NSRect(origin: origin, size: NSSize(width: width, height: height))\n    }\n\n    private static func appendDebugLog(_ message: String) {\n        let timestamp = ISO8601DateFormatter().string(from: Date())\n        let line = \"[\\(timestamp)] \\(LogRedactor.redact(message))\\n\"\n        guard let data = line.data(using: .utf8) else { return }\n        if FileManager.default.fileExists(atPath: Self.debugLogURL.path) {\n            if let handle = try? FileHandle(forWritingTo: Self.debugLogURL) {\n                handle.seekToEndOfFile()\n                handle.write(data)\n                try? handle.close()\n            }\n        } else {\n            try? data.write(to: Self.debugLogURL, options: .atomic)\n        }\n    }\n\n    private static func resetDebugLog() {\n        try? FileManager.default.removeItem(at: self.debugLogURL)\n    }\n\n    private static func sanitizedURLString(_ url: URL) -> String {\n        guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {\n            return url.absoluteString\n        }\n        components.query = nil\n        components.fragment = nil\n        return components.string ?? url.absoluteString\n    }\n}\n\nprivate final class WeakScriptMessageHandler: NSObject, WKScriptMessageHandler {\n    weak var delegate: WKScriptMessageHandler?\n\n    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {\n        self.delegate?.userContentController(userContentController, didReceive: message)\n    }\n}\n\n// MARK: - NSWindowDelegate\n\nextension OpenAICreditsPurchaseWindowController: NSWindowDelegate {\n    func windowWillClose(_ notification: Notification) {\n        guard let window = self.window else { return }\n        let webView = self.webView\n        self.pendingAutoStart = false\n        self.webView = nil\n        self.window = nil\n        self.logger.info(\"Buy credits window closing\")\n        WebKitTeardown.scheduleCleanup(owner: window, window: window, webView: webView)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/PersonalInfoRedactor.swift",
    "content": "import Foundation\n\nenum PersonalInfoRedactor {\n    static let emailPlaceholder = \"Hidden\"\n\n    private static let emailRegex: NSRegularExpression? = {\n        let pattern = #\"[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}\"#\n        return try? NSRegularExpression(pattern: pattern, options: [.caseInsensitive])\n    }()\n\n    static func redactEmail(_ email: String?, isEnabled: Bool) -> String {\n        guard let email, !email.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return \"\" }\n        guard isEnabled else { return email }\n        return Self.emailPlaceholder\n    }\n\n    static func redactEmails(in text: String?, isEnabled: Bool) -> String? {\n        guard let text else { return nil }\n        guard isEnabled else { return text }\n        guard let regex = Self.emailRegex else { return text }\n        let range = NSRange(text.startIndex..<text.endIndex, in: text)\n        return regex.stringByReplacingMatches(\n            in: text,\n            options: [],\n            range: range,\n            withTemplate: Self.emailPlaceholder)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/PreferencesAboutPane.swift",
    "content": "import AppKit\nimport SwiftUI\n\n@MainActor\nstruct AboutPane: View {\n    let updater: UpdaterProviding\n    @State private var iconHover = false\n    @AppStorage(\"autoUpdateEnabled\") private var autoUpdateEnabled: Bool = true\n    @AppStorage(UpdateChannel.userDefaultsKey)\n    private var updateChannelRaw: String = UpdateChannel.defaultChannel.rawValue\n    @State private var didLoadUpdaterState = false\n\n    private var versionString: String {\n        let version = Bundle.main.object(forInfoDictionaryKey: \"CFBundleShortVersionString\") as? String ?? \"–\"\n        let build = Bundle.main.object(forInfoDictionaryKey: \"CFBundleVersion\") as? String\n        return build.map { \"\\(version) (\\($0))\" } ?? version\n    }\n\n    private var buildTimestamp: String? {\n        guard let raw = Bundle.main.object(forInfoDictionaryKey: \"CodexBuildTimestamp\") as? String else { return nil }\n        let parser = ISO8601DateFormatter()\n        parser.formatOptions = [.withInternetDateTime]\n        guard let date = parser.date(from: raw) else { return raw }\n\n        let formatter = DateFormatter()\n        formatter.dateStyle = .medium\n        formatter.timeStyle = .short\n        formatter.locale = .current\n        return formatter.string(from: date)\n    }\n\n    var body: some View {\n        VStack(spacing: 12) {\n            if let image = NSApplication.shared.applicationIconImage {\n                Button(action: self.openProjectHome) {\n                    Image(nsImage: image)\n                        .resizable()\n                        .frame(width: 92, height: 92)\n                        .cornerRadius(16)\n                        .scaleEffect(self.iconHover ? 1.05 : 1.0)\n                        .shadow(color: self.iconHover ? .accentColor.opacity(0.25) : .clear, radius: 6)\n                }\n                .buttonStyle(.plain)\n                .onHover { hovering in\n                    withAnimation(.spring(response: 0.32, dampingFraction: 0.78)) {\n                        self.iconHover = hovering\n                    }\n                }\n            }\n\n            VStack(spacing: 2) {\n                Text(\"CodexBar\")\n                    .font(.title3).bold()\n                Text(\"Version \\(self.versionString)\")\n                    .foregroundStyle(.secondary)\n                if let buildTimestamp {\n                    Text(\"Built \\(buildTimestamp)\")\n                        .font(.footnote)\n                        .foregroundStyle(.secondary)\n                }\n                Text(\"May your tokens never run out—keep agent limits in view.\")\n                    .font(.footnote)\n                    .foregroundStyle(.secondary)\n            }\n\n            VStack(alignment: .center, spacing: 10) {\n                AboutLinkRow(\n                    icon: \"chevron.left.slash.chevron.right\",\n                    title: \"GitHub\",\n                    url: \"https://github.com/steipete/CodexBar\")\n                AboutLinkRow(icon: \"globe\", title: \"Website\", url: \"https://steipete.me\")\n                AboutLinkRow(icon: \"bird\", title: \"Twitter\", url: \"https://twitter.com/steipete\")\n                AboutLinkRow(icon: \"envelope\", title: \"Email\", url: \"mailto:peter@steipete.me\")\n            }\n            .padding(.top, 8)\n            .frame(maxWidth: .infinity)\n            .multilineTextAlignment(.center)\n\n            Divider()\n\n            if self.updater.isAvailable {\n                VStack(spacing: 10) {\n                    Toggle(\"Check for updates automatically\", isOn: self.$autoUpdateEnabled)\n                        .toggleStyle(.checkbox)\n                        .frame(maxWidth: .infinity, alignment: .center)\n                    VStack(spacing: 6) {\n                        HStack(spacing: 12) {\n                            Text(\"Update Channel\")\n                            Spacer()\n                            Picker(\"\", selection: self.updateChannelBinding) {\n                                ForEach(UpdateChannel.allCases) { channel in\n                                    Text(channel.displayName).tag(channel)\n                                }\n                            }\n                            .pickerStyle(.menu)\n                            .labelsHidden()\n                        }\n                        .frame(maxWidth: 280)\n                        Text(self.updateChannel.description)\n                            .font(.footnote)\n                            .foregroundStyle(.secondary)\n                            .multilineTextAlignment(.center)\n                            .frame(maxWidth: 280)\n                    }\n                    Button(\"Check for Updates…\") { self.updater.checkForUpdates(nil) }\n                }\n            } else {\n                Text(self.updater.unavailableReason ?? \"Updates unavailable in this build.\")\n                    .foregroundStyle(.secondary)\n            }\n\n            Text(\"© 2025 Peter Steinberger. MIT License.\")\n                .font(.footnote)\n                .foregroundStyle(.secondary)\n                .padding(.top, 4)\n\n            Spacer(minLength: 0)\n        }\n        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)\n        .padding(.top, 4)\n        .padding(.horizontal, 24)\n        .padding(.bottom, 24)\n        .onAppear {\n            guard !self.didLoadUpdaterState else { return }\n            // Align Sparkle's flag with the persisted preference on first load.\n            self.updater.automaticallyChecksForUpdates = self.autoUpdateEnabled\n            self.updater.automaticallyDownloadsUpdates = self.autoUpdateEnabled\n            self.didLoadUpdaterState = true\n        }\n        .onChange(of: self.autoUpdateEnabled) { _, newValue in\n            self.updater.automaticallyChecksForUpdates = newValue\n            self.updater.automaticallyDownloadsUpdates = newValue\n        }\n    }\n\n    private var updateChannel: UpdateChannel {\n        UpdateChannel(rawValue: self.updateChannelRaw) ?? .stable\n    }\n\n    private var updateChannelBinding: Binding<UpdateChannel> {\n        Binding(\n            get: { self.updateChannel },\n            set: { newValue in\n                self.updateChannelRaw = newValue.rawValue\n                self.updater.checkForUpdates(nil)\n            })\n    }\n\n    private func openProjectHome() {\n        guard let url = URL(string: \"https://github.com/steipete/CodexBar\") else { return }\n        NSWorkspace.shared.open(url)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/PreferencesAdvancedPane.swift",
    "content": "import KeyboardShortcuts\nimport SwiftUI\n\n@MainActor\nstruct AdvancedPane: View {\n    @Bindable var settings: SettingsStore\n    @State private var isInstallingCLI = false\n    @State private var cliStatus: String?\n\n    var body: some View {\n        ScrollView(.vertical, showsIndicators: true) {\n            VStack(alignment: .leading, spacing: 16) {\n                SettingsSection(contentSpacing: 8) {\n                    Text(\"Keyboard shortcut\")\n                        .font(.caption)\n                        .foregroundStyle(.secondary)\n                        .textCase(.uppercase)\n                    HStack(alignment: .center, spacing: 12) {\n                        Text(\"Open menu\")\n                            .font(.body)\n                        Spacer()\n                        KeyboardShortcuts.Recorder(for: .openMenu)\n                    }\n                    Text(\"Trigger the menu bar menu from anywhere.\")\n                        .font(.footnote)\n                        .foregroundStyle(.tertiary)\n                }\n\n                Divider()\n\n                SettingsSection(contentSpacing: 10) {\n                    HStack(spacing: 12) {\n                        Button {\n                            Task { await self.installCLI() }\n                        } label: {\n                            if self.isInstallingCLI {\n                                ProgressView().controlSize(.small)\n                            } else {\n                                Text(\"Install CLI\")\n                            }\n                        }\n                        .disabled(self.isInstallingCLI)\n\n                        if let status = self.cliStatus {\n                            Text(status)\n                                .font(.footnote)\n                                .foregroundStyle(.tertiary)\n                                .lineLimit(2)\n                        }\n                    }\n                    Text(\"Symlink CodexBarCLI to /usr/local/bin and /opt/homebrew/bin as codexbar.\")\n                        .font(.footnote)\n                        .foregroundStyle(.tertiary)\n                }\n\n                Divider()\n\n                SettingsSection(contentSpacing: 10) {\n                    PreferenceToggleRow(\n                        title: \"Show Debug Settings\",\n                        subtitle: \"Expose troubleshooting tools in the Debug tab.\",\n                        binding: self.$settings.debugMenuEnabled)\n                    PreferenceToggleRow(\n                        title: \"Surprise me\",\n                        subtitle: \"Check if you like your agents having some fun up there.\",\n                        binding: self.$settings.randomBlinkEnabled)\n                }\n\n                Divider()\n\n                SettingsSection(contentSpacing: 10) {\n                    PreferenceToggleRow(\n                        title: \"Hide personal information\",\n                        subtitle: \"Obscure email addresses in the menu bar and menu UI.\",\n                        binding: self.$settings.hidePersonalInfo)\n                }\n\n                Divider()\n\n                SettingsSection(\n                    title: \"Keychain access\",\n                    caption: \"\"\"\n                    Disable all Keychain reads and writes. Browser cookie import is unavailable; paste Cookie \\\n                    headers manually in Providers.\n                    \"\"\") {\n                        PreferenceToggleRow(\n                            title: \"Disable Keychain access\",\n                            subtitle: \"Prevents any Keychain access while enabled.\",\n                            binding: self.$settings.debugDisableKeychainAccess)\n                    }\n            }\n            .frame(maxWidth: .infinity, alignment: .leading)\n            .padding(.horizontal, 20)\n            .padding(.vertical, 12)\n        }\n    }\n}\n\nextension AdvancedPane {\n    private func installCLI() async {\n        if self.isInstallingCLI { return }\n        self.isInstallingCLI = true\n        defer { self.isInstallingCLI = false }\n\n        let helperURL = Bundle.main.bundleURL.appendingPathComponent(\"Contents/Helpers/CodexBarCLI\")\n        let fm = FileManager.default\n        guard fm.fileExists(atPath: helperURL.path) else {\n            self.cliStatus = \"CodexBarCLI not found in app bundle.\"\n            return\n        }\n\n        let destinations = [\n            \"/usr/local/bin/codexbar\",\n            \"/opt/homebrew/bin/codexbar\",\n        ]\n\n        var results: [String] = []\n        for dest in destinations {\n            let dir = (dest as NSString).deletingLastPathComponent\n            guard fm.fileExists(atPath: dir) else { continue }\n            guard fm.isWritableFile(atPath: dir) else {\n                results.append(\"No write access: \\(dir)\")\n                continue\n            }\n\n            if fm.fileExists(atPath: dest) {\n                if Self.isLink(atPath: dest, pointingTo: helperURL.path) {\n                    results.append(\"Installed: \\(dir)\")\n                } else {\n                    results.append(\"Exists: \\(dir)\")\n                }\n                continue\n            }\n\n            do {\n                try fm.createSymbolicLink(atPath: dest, withDestinationPath: helperURL.path)\n                results.append(\"Installed: \\(dir)\")\n            } catch {\n                results.append(\"Failed: \\(dir)\")\n            }\n        }\n\n        self.cliStatus = results.isEmpty\n            ? \"No writable bin dirs found.\"\n            : results.joined(separator: \" · \")\n    }\n\n    private static func isLink(atPath path: String, pointingTo destination: String) -> Bool {\n        guard let link = try? FileManager.default.destinationOfSymbolicLink(atPath: path) else { return false }\n        let dir = (path as NSString).deletingLastPathComponent\n        let resolved = URL(fileURLWithPath: link, relativeTo: URL(fileURLWithPath: dir))\n            .standardizedFileURL\n            .path\n        return resolved == destination\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/PreferencesComponents.swift",
    "content": "import AppKit\nimport SwiftUI\n\n@MainActor\nstruct PreferenceToggleRow: View {\n    let title: String\n    let subtitle: String?\n    @Binding var binding: Bool\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 5.4) {\n            Toggle(isOn: self.$binding) {\n                Text(self.title)\n                    .font(.body)\n            }\n            .toggleStyle(.checkbox)\n\n            if let subtitle, !subtitle.isEmpty {\n                Text(subtitle)\n                    .font(.footnote)\n                    .foregroundStyle(.tertiary)\n                    .fixedSize(horizontal: false, vertical: true)\n            }\n        }\n    }\n}\n\n@MainActor\nstruct SettingsSection<Content: View>: View {\n    let title: String?\n    let caption: String?\n    let contentSpacing: CGFloat\n    private let content: () -> Content\n\n    init(\n        title: String? = nil,\n        caption: String? = nil,\n        contentSpacing: CGFloat = 14,\n        @ViewBuilder content: @escaping () -> Content)\n    {\n        self.title = title\n        self.caption = caption\n        self.contentSpacing = contentSpacing\n        self.content = content\n    }\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 10) {\n            if let title, !title.isEmpty {\n                Text(title)\n                    .font(.subheadline.weight(.semibold))\n            }\n            if let caption {\n                Text(caption)\n                    .font(.footnote)\n                    .foregroundStyle(.tertiary)\n                    .fixedSize(horizontal: false, vertical: true)\n            }\n            VStack(alignment: .leading, spacing: self.contentSpacing) {\n                self.content()\n            }\n            .frame(maxWidth: .infinity, alignment: .leading)\n        }\n    }\n}\n\n@MainActor\nstruct AboutLinkRow: View {\n    let icon: String\n    let title: String\n    let url: String\n    @State private var hovering = false\n\n    var body: some View {\n        Button {\n            if let url = URL(string: self.url) { NSWorkspace.shared.open(url) }\n        } label: {\n            HStack(spacing: 8) {\n                Image(systemName: self.icon)\n                Text(self.title)\n                    .underline(self.hovering, color: .accentColor)\n            }\n            .frame(maxWidth: .infinity)\n            .padding(.vertical, 4)\n            .foregroundColor(.accentColor)\n        }\n        .buttonStyle(.plain)\n        .contentShape(Rectangle())\n        .onHover { self.hovering = $0 }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/PreferencesDebugPane.swift",
    "content": "import AppKit\nimport CodexBarCore\nimport SwiftUI\n\n@MainActor\nstruct DebugPane: View {\n    @Bindable var settings: SettingsStore\n    @Bindable var store: UsageStore\n    @AppStorage(\"debugFileLoggingEnabled\") private var debugFileLoggingEnabled = false\n    @State private var currentLogProvider: UsageProvider = .codex\n    @State private var currentFetchProvider: UsageProvider = .codex\n    @State private var isLoadingLog = false\n    @State private var logText: String = \"\"\n    @State private var isClearingCostCache = false\n    @State private var costCacheStatus: String?\n    #if DEBUG\n    @State private var currentErrorProvider: UsageProvider = .codex\n    @State private var simulatedErrorText: String = \"\"\"\n    Simulated error for testing layout.\n    Second line.\n    Third line.\n    Fourth line.\n    \"\"\"\n    #endif\n\n    var body: some View {\n        ScrollView(.vertical, showsIndicators: true) {\n            VStack(alignment: .leading, spacing: 20) {\n                SettingsSection(title: \"Logging\") {\n                    PreferenceToggleRow(\n                        title: \"Enable file logging\",\n                        subtitle: \"Write logs to \\(self.fileLogPath) for debugging.\",\n                        binding: self.$debugFileLoggingEnabled)\n                        .onChange(of: self.debugFileLoggingEnabled) { _, newValue in\n                            if self.settings.debugFileLoggingEnabled != newValue {\n                                self.settings.debugFileLoggingEnabled = newValue\n                            }\n                        }\n\n                    HStack(alignment: .center, spacing: 12) {\n                        VStack(alignment: .leading, spacing: 4) {\n                            Text(\"Verbosity\")\n                                .font(.body)\n                            Text(\"Controls how much detail is logged.\")\n                                .font(.footnote)\n                                .foregroundStyle(.tertiary)\n                        }\n                        Spacer()\n                        Picker(\"Verbosity\", selection: self.$settings.debugLogLevel) {\n                            ForEach(CodexBarLog.Level.allCases) { level in\n                                Text(level.displayName).tag(level)\n                            }\n                        }\n                        .labelsHidden()\n                        .pickerStyle(.menu)\n                        .frame(maxWidth: 160)\n                    }\n\n                    Button {\n                        NSWorkspace.shared.open(CodexBarLog.fileLogURL)\n                    } label: {\n                        Label(\"Open log file\", systemImage: \"doc.text.magnifyingglass\")\n                    }\n                    .controlSize(.small)\n                }\n\n                SettingsSection {\n                    PreferenceToggleRow(\n                        title: \"Force animation on next refresh\",\n                        subtitle: \"Temporarily shows the loading animation after the next refresh.\",\n                        binding: self.$store.debugForceAnimation)\n                }\n\n                SettingsSection(\n                    title: \"Loading animations\",\n                    caption: \"Pick a pattern and replay it in the menu bar. \\\"Random\\\" keeps the existing behavior.\")\n                {\n                    Picker(\"Animation pattern\", selection: self.animationPatternBinding) {\n                        Text(\"Random (default)\").tag(nil as LoadingPattern?)\n                        ForEach(LoadingPattern.allCases) { pattern in\n                            Text(pattern.displayName).tag(Optional(pattern))\n                        }\n                    }\n                    .pickerStyle(.radioGroup)\n\n                    Button(\"Replay selected animation\") {\n                        self.replaySelectedAnimation()\n                    }\n                    .keyboardShortcut(.defaultAction)\n\n                    Button {\n                        NotificationCenter.default.post(name: .codexbarDebugBlinkNow, object: nil)\n                    } label: {\n                        Label(\"Blink now\", systemImage: \"eyes\")\n                    }\n                    .controlSize(.small)\n                }\n\n                SettingsSection(\n                    title: \"Probe logs\",\n                    caption: \"Fetch the latest probe output for debugging; Copy keeps the full text.\")\n                {\n                    Picker(\"Provider\", selection: self.$currentLogProvider) {\n                        Text(\"Codex\").tag(UsageProvider.codex)\n                        Text(\"Claude\").tag(UsageProvider.claude)\n                        Text(\"Cursor\").tag(UsageProvider.cursor)\n                        Text(\"Augment\").tag(UsageProvider.augment)\n                        Text(\"Amp\").tag(UsageProvider.amp)\n                        Text(\"Ollama\").tag(UsageProvider.ollama)\n                    }\n                    .pickerStyle(.segmented)\n                    .frame(width: 460)\n\n                    HStack(spacing: 12) {\n                        Button { self.loadLog(self.currentLogProvider) } label: {\n                            Label(\"Fetch log\", systemImage: \"arrow.clockwise\")\n                        }\n                        .disabled(self.isLoadingLog)\n\n                        Button { self.copyToPasteboard(self.logText) } label: {\n                            Label(\"Copy\", systemImage: \"doc.on.doc\")\n                        }\n                        .disabled(self.logText.isEmpty)\n\n                        Button { self.saveLog(self.currentLogProvider) } label: {\n                            Label(\"Save to file\", systemImage: \"externaldrive.badge.plus\")\n                        }\n                        .disabled(self.isLoadingLog && self.logText.isEmpty)\n\n                        if self.currentLogProvider == .claude {\n                            Button { self.loadClaudeDump() } label: {\n                                Label(\"Load parse dump\", systemImage: \"doc.text.magnifyingglass\")\n                            }\n                            .disabled(self.isLoadingLog)\n                        }\n                    }\n\n                    Button {\n                        self.settings.rerunProviderDetection()\n                        self.loadLog(self.currentLogProvider)\n                    } label: {\n                        Label(\"Re-run provider autodetect\", systemImage: \"dot.radiowaves.left.and.right\")\n                    }\n                    .controlSize(.small)\n\n                    ZStack(alignment: .topLeading) {\n                        ScrollView {\n                            Text(self.displayedLog)\n                                .font(.system(.footnote, design: .monospaced))\n                                .textSelection(.enabled)\n                                .frame(maxWidth: .infinity, alignment: .leading)\n                                .padding(8)\n                        }\n                        .frame(minHeight: 160, maxHeight: 220)\n                        .background(Color(NSColor.textBackgroundColor))\n                        .cornerRadius(6)\n\n                        if self.isLoadingLog {\n                            ProgressView()\n                                .progressViewStyle(.circular)\n                                .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)\n                                .padding()\n                        }\n                    }\n                }\n\n                SettingsSection(\n                    title: \"Fetch strategy attempts\",\n                    caption: \"Last fetch pipeline decisions and errors for a provider.\")\n                {\n                    Picker(\"Provider\", selection: self.$currentFetchProvider) {\n                        ForEach(UsageProvider.allCases, id: \\.self) { provider in\n                            Text(provider.rawValue.capitalized).tag(provider)\n                        }\n                    }\n                    .pickerStyle(.menu)\n                    .frame(width: 240)\n\n                    ScrollView {\n                        Text(self.fetchAttemptsText(for: self.currentFetchProvider))\n                            .font(.system(.footnote, design: .monospaced))\n                            .textSelection(.enabled)\n                            .frame(maxWidth: .infinity, alignment: .leading)\n                            .padding(8)\n                    }\n                    .frame(minHeight: 120, maxHeight: 220)\n                    .background(Color(NSColor.textBackgroundColor))\n                    .cornerRadius(6)\n                }\n\n                if !self.settings.debugDisableKeychainAccess {\n                    SettingsSection(\n                        title: \"OpenAI cookies\",\n                        caption: \"Cookie import + WebKit scrape logs from the last OpenAI cookies attempt.\")\n                    {\n                        HStack(spacing: 12) {\n                            Button {\n                                self.copyToPasteboard(self.store.openAIDashboardCookieImportDebugLog ?? \"\")\n                            } label: {\n                                Label(\"Copy\", systemImage: \"doc.on.doc\")\n                            }\n                            .disabled((self.store.openAIDashboardCookieImportDebugLog ?? \"\").isEmpty)\n                        }\n\n                        ScrollView {\n                            Text(\n                                self.store.openAIDashboardCookieImportDebugLog?.isEmpty == false\n                                    ? (self.store.openAIDashboardCookieImportDebugLog ?? \"\")\n                                    : \"No log yet. Update OpenAI cookies in Providers → Codex to run an import.\")\n                                .font(.system(.footnote, design: .monospaced))\n                                .textSelection(.enabled)\n                                .frame(maxWidth: .infinity, alignment: .leading)\n                                .padding(8)\n                        }\n                        .frame(minHeight: 120, maxHeight: 180)\n                        .background(Color(NSColor.textBackgroundColor))\n                        .cornerRadius(6)\n                    }\n                }\n\n                SettingsSection(\n                    title: \"Caches\",\n                    caption: \"Clear cached cost scan results.\")\n                {\n                    let isTokenRefreshActive = self.store.isTokenRefreshInFlight(for: .codex)\n                        || self.store.isTokenRefreshInFlight(for: .claude)\n\n                    HStack(spacing: 12) {\n                        Button {\n                            Task { await self.clearCostCache() }\n                        } label: {\n                            Label(\"Clear cost cache\", systemImage: \"trash\")\n                        }\n                        .disabled(self.isClearingCostCache || isTokenRefreshActive)\n\n                        if let status = self.costCacheStatus {\n                            Text(status)\n                                .font(.footnote)\n                                .foregroundStyle(.tertiary)\n                        }\n                    }\n                }\n\n                SettingsSection(\n                    title: \"Notifications\",\n                    caption: \"Trigger test notifications for the 5-hour session window (depleted/restored).\")\n                {\n                    Picker(\"Provider\", selection: self.$currentLogProvider) {\n                        Text(\"Codex\").tag(UsageProvider.codex)\n                        Text(\"Claude\").tag(UsageProvider.claude)\n                    }\n                    .pickerStyle(.segmented)\n                    .frame(width: 240)\n\n                    HStack(spacing: 12) {\n                        Button {\n                            self.postSessionNotification(.depleted, provider: self.currentLogProvider)\n                        } label: {\n                            Label(\"Post depleted\", systemImage: \"bell.badge\")\n                        }\n                        .controlSize(.small)\n\n                        Button {\n                            self.postSessionNotification(.restored, provider: self.currentLogProvider)\n                        } label: {\n                            Label(\"Post restored\", systemImage: \"bell\")\n                        }\n                        .controlSize(.small)\n                    }\n                }\n\n                SettingsSection(\n                    title: \"CLI sessions\",\n                    caption: \"Keep Codex/Claude CLI sessions alive after a probe. Default exits once data is captured.\")\n                {\n                    PreferenceToggleRow(\n                        title: \"Keep CLI sessions alive\",\n                        subtitle: \"Skip teardown between probes (debug-only).\",\n                        binding: self.$settings.debugKeepCLISessionsAlive)\n\n                    Button {\n                        Task {\n                            await CLIProbeSessionResetter.resetAll()\n                        }\n                    } label: {\n                        Label(\"Reset CLI sessions\", systemImage: \"arrow.counterclockwise\")\n                    }\n                    .controlSize(.small)\n                }\n\n                #if DEBUG\n                SettingsSection(\n                    title: \"Error simulation\",\n                    caption: \"Inject a fake error message into the menu card for layout testing.\")\n                {\n                    Picker(\"Provider\", selection: self.$currentErrorProvider) {\n                        Text(\"Codex\").tag(UsageProvider.codex)\n                        Text(\"Claude\").tag(UsageProvider.claude)\n                        Text(\"Gemini\").tag(UsageProvider.gemini)\n                        Text(\"Antigravity\").tag(UsageProvider.antigravity)\n                        Text(\"Augment\").tag(UsageProvider.augment)\n                        Text(\"Amp\").tag(UsageProvider.amp)\n                        Text(\"Ollama\").tag(UsageProvider.ollama)\n                    }\n                    .pickerStyle(.segmented)\n                    .frame(width: 360)\n\n                    TextField(\"Simulated error text\", text: self.$simulatedErrorText, axis: .vertical)\n                        .lineLimit(4)\n\n                    HStack(spacing: 12) {\n                        Button {\n                            self.store._setErrorForTesting(\n                                self.simulatedErrorText,\n                                provider: self.currentErrorProvider)\n                        } label: {\n                            Label(\"Set menu error\", systemImage: \"exclamationmark.triangle\")\n                        }\n                        .controlSize(.small)\n\n                        Button {\n                            self.store._setErrorForTesting(nil, provider: self.currentErrorProvider)\n                        } label: {\n                            Label(\"Clear menu error\", systemImage: \"xmark.circle\")\n                        }\n                        .controlSize(.small)\n                    }\n\n                    let supportsTokenError = self.currentErrorProvider == .codex || self.currentErrorProvider == .claude\n                    HStack(spacing: 12) {\n                        Button {\n                            self.store._setTokenErrorForTesting(\n                                self.simulatedErrorText,\n                                provider: self.currentErrorProvider)\n                        } label: {\n                            Label(\"Set cost error\", systemImage: \"banknote\")\n                        }\n                        .controlSize(.small)\n                        .disabled(!supportsTokenError)\n\n                        Button {\n                            self.store._setTokenErrorForTesting(nil, provider: self.currentErrorProvider)\n                        } label: {\n                            Label(\"Clear cost error\", systemImage: \"xmark.circle\")\n                        }\n                        .controlSize(.small)\n                        .disabled(!supportsTokenError)\n                    }\n                }\n                #endif\n\n                SettingsSection(\n                    title: \"CLI paths\",\n                    caption: \"Resolved Codex binary and PATH layers; startup login PATH capture (short timeout).\")\n                {\n                    self.binaryRow(title: \"Codex binary\", value: self.store.pathDebugInfo.codexBinary)\n                    self.binaryRow(title: \"Claude binary\", value: self.store.pathDebugInfo.claudeBinary)\n\n                    VStack(alignment: .leading, spacing: 6) {\n                        Text(\"Effective PATH\")\n                            .font(.callout.weight(.semibold))\n                        ScrollView {\n                            Text(\n                                self.store.pathDebugInfo.effectivePATH.isEmpty\n                                    ? \"Unavailable\"\n                                    : self.store.pathDebugInfo.effectivePATH)\n                                .font(.system(.footnote, design: .monospaced))\n                                .textSelection(.enabled)\n                                .frame(maxWidth: .infinity, alignment: .leading)\n                                .padding(6)\n                        }\n                        .frame(minHeight: 60, maxHeight: 110)\n                        .background(Color(NSColor.textBackgroundColor))\n                        .cornerRadius(6)\n                    }\n\n                    if let loginPATH = self.store.pathDebugInfo.loginShellPATH {\n                        VStack(alignment: .leading, spacing: 6) {\n                            Text(\"Login shell PATH (startup capture)\")\n                                .font(.callout.weight(.semibold))\n                            ScrollView {\n                                Text(loginPATH)\n                                    .font(.system(.footnote, design: .monospaced))\n                                    .textSelection(.enabled)\n                                    .frame(maxWidth: .infinity, alignment: .leading)\n                                    .padding(6)\n                            }\n                            .frame(minHeight: 60, maxHeight: 110)\n                            .background(Color(NSColor.textBackgroundColor))\n                            .cornerRadius(6)\n                        }\n                    }\n                }\n            }\n            .frame(maxWidth: .infinity, alignment: .leading)\n            .padding(.horizontal, 20)\n            .padding(.vertical, 12)\n        }\n    }\n\n    private var fileLogPath: String {\n        CodexBarLog.fileLogURL.path\n    }\n\n    private var animationPatternBinding: Binding<LoadingPattern?> {\n        Binding(\n            get: { self.settings.debugLoadingPattern },\n            set: { self.settings.debugLoadingPattern = $0 })\n    }\n\n    private func replaySelectedAnimation() {\n        var userInfo: [AnyHashable: Any] = [:]\n        if let pattern = self.settings.debugLoadingPattern {\n            userInfo[\"pattern\"] = pattern.rawValue\n        }\n        NotificationCenter.default.post(\n            name: .codexbarDebugReplayAllAnimations,\n            object: nil,\n            userInfo: userInfo.isEmpty ? nil : userInfo)\n        self.store.replayLoadingAnimation(duration: 4)\n    }\n\n    private var displayedLog: String {\n        if self.logText.isEmpty {\n            return self.isLoadingLog ? \"Loading…\" : \"No log yet. Fetch to load.\"\n        }\n        return self.logText\n    }\n\n    private func loadLog(_ provider: UsageProvider) {\n        self.isLoadingLog = true\n        Task {\n            let text = await ProviderInteractionContext.$current.withValue(.userInitiated) {\n                await ProviderRefreshContext.$current.withValue(.regular) {\n                    await self.store.debugLog(for: provider)\n                }\n            }\n            await MainActor.run {\n                self.logText = text\n                self.isLoadingLog = false\n            }\n        }\n    }\n\n    private func saveLog(_ provider: UsageProvider) {\n        Task {\n            if self.logText.isEmpty {\n                self.isLoadingLog = true\n                let text = await ProviderInteractionContext.$current.withValue(.userInitiated) {\n                    await ProviderRefreshContext.$current.withValue(.regular) {\n                        await self.store.debugLog(for: provider)\n                    }\n                }\n                await MainActor.run { self.logText = text }\n                self.isLoadingLog = false\n            }\n            _ = await ProviderInteractionContext.$current.withValue(.userInitiated) {\n                await ProviderRefreshContext.$current.withValue(.regular) {\n                    await self.store.dumpLog(toFileFor: provider)\n                }\n            }\n        }\n    }\n\n    private func copyToPasteboard(_ text: String) {\n        let pb = NSPasteboard.general\n        pb.clearContents()\n        pb.setString(text, forType: .string)\n    }\n\n    private func binaryRow(title: String, value: String?) -> some View {\n        VStack(alignment: .leading, spacing: 6) {\n            Text(title)\n                .font(.callout.weight(.semibold))\n            Text(value ?? \"Not found\")\n                .font(.system(.footnote, design: .monospaced))\n                .foregroundStyle(value == nil ? .secondary : .primary)\n        }\n    }\n\n    private func loadClaudeDump() {\n        self.isLoadingLog = true\n        Task {\n            let text = await self.store.debugClaudeDump()\n            await MainActor.run {\n                self.logText = text\n                self.isLoadingLog = false\n            }\n        }\n    }\n\n    private func postSessionNotification(_ transition: SessionQuotaTransition, provider: UsageProvider) {\n        SessionQuotaNotifier().post(transition: transition, provider: provider, badge: 1)\n    }\n\n    private func clearCostCache() async {\n        guard !self.isClearingCostCache else { return }\n        self.isClearingCostCache = true\n        self.costCacheStatus = nil\n        defer { self.isClearingCostCache = false }\n\n        if let error = await self.store.clearCostUsageCache() {\n            self.costCacheStatus = \"Failed: \\(error)\"\n            return\n        }\n\n        self.costCacheStatus = \"Cleared.\"\n    }\n\n    private func fetchAttemptsText(for provider: UsageProvider) -> String {\n        let attempts = self.store.fetchAttempts(for: provider)\n        guard !attempts.isEmpty else { return \"No fetch attempts yet.\" }\n        return attempts.map { attempt in\n            let kind = Self.fetchKindLabel(attempt.kind)\n            var line = \"\\(attempt.strategyID) (\\(kind))\"\n            line += attempt.wasAvailable ? \" available\" : \" unavailable\"\n            if let error = attempt.errorDescription, !error.isEmpty {\n                line += \" error=\\(error)\"\n            }\n            return line\n        }.joined(separator: \"\\n\")\n    }\n\n    private static func fetchKindLabel(_ kind: ProviderFetchKind) -> String {\n        switch kind {\n        case .cli: \"cli\"\n        case .web: \"web\"\n        case .oauth: \"oauth\"\n        case .apiToken: \"api\"\n        case .localProbe: \"local\"\n        case .webDashboard: \"web\"\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/PreferencesDisplayPane.swift",
    "content": "import CodexBarCore\nimport SwiftUI\n\n@MainActor\nstruct DisplayPane: View {\n    private static let maxOverviewProviders = SettingsStore.mergedOverviewProviderLimit\n\n    @State private var isOverviewProviderPopoverPresented = false\n    @Bindable var settings: SettingsStore\n    @Bindable var store: UsageStore\n\n    var body: some View {\n        ScrollView(.vertical, showsIndicators: true) {\n            VStack(alignment: .leading, spacing: 16) {\n                SettingsSection(contentSpacing: 12) {\n                    Text(\"Menu bar\")\n                        .font(.caption)\n                        .foregroundStyle(.secondary)\n                        .textCase(.uppercase)\n                    PreferenceToggleRow(\n                        title: \"Merge Icons\",\n                        subtitle: \"Use a single menu bar icon with a provider switcher.\",\n                        binding: self.$settings.mergeIcons)\n                    PreferenceToggleRow(\n                        title: \"Switcher shows icons\",\n                        subtitle: \"Show provider icons in the switcher (otherwise show a weekly progress line).\",\n                        binding: self.$settings.switcherShowsIcons)\n                        .disabled(!self.settings.mergeIcons)\n                        .opacity(self.settings.mergeIcons ? 1 : 0.5)\n                    PreferenceToggleRow(\n                        title: \"Show most-used provider\",\n                        subtitle: \"Menu bar auto-shows the provider closest to its rate limit.\",\n                        binding: self.$settings.menuBarShowsHighestUsage)\n                        .disabled(!self.settings.mergeIcons)\n                        .opacity(self.settings.mergeIcons ? 1 : 0.5)\n                    PreferenceToggleRow(\n                        title: \"Menu bar shows percent\",\n                        subtitle: \"Replace critter bars with provider branding icons and a percentage.\",\n                        binding: self.$settings.menuBarShowsBrandIconWithPercent)\n                    HStack(alignment: .top, spacing: 12) {\n                        VStack(alignment: .leading, spacing: 4) {\n                            Text(\"Display mode\")\n                                .font(.body)\n                            Text(\"Choose what to show in the menu bar (Pace shows usage vs. expected).\")\n                                .font(.footnote)\n                                .foregroundStyle(.tertiary)\n                        }\n                        Spacer()\n                        Picker(\"Display mode\", selection: self.$settings.menuBarDisplayMode) {\n                            ForEach(MenuBarDisplayMode.allCases) { mode in\n                                Text(mode.label).tag(mode)\n                            }\n                        }\n                        .labelsHidden()\n                        .pickerStyle(.menu)\n                        .frame(maxWidth: 200)\n                    }\n                    .disabled(!self.settings.menuBarShowsBrandIconWithPercent)\n                    .opacity(self.settings.menuBarShowsBrandIconWithPercent ? 1 : 0.5)\n                }\n\n                Divider()\n\n                SettingsSection(contentSpacing: 12) {\n                    Text(\"Menu content\")\n                        .font(.caption)\n                        .foregroundStyle(.secondary)\n                        .textCase(.uppercase)\n                    PreferenceToggleRow(\n                        title: \"Show usage as used\",\n                        subtitle: \"Progress bars fill as you consume quota (instead of showing remaining).\",\n                        binding: self.$settings.usageBarsShowUsed)\n                    PreferenceToggleRow(\n                        title: \"Show reset time as clock\",\n                        subtitle: \"Display reset times as absolute clock values instead of countdowns.\",\n                        binding: self.$settings.resetTimesShowAbsolute)\n                    PreferenceToggleRow(\n                        title: \"Show credits + extra usage\",\n                        subtitle: \"Show Codex Credits and Claude Extra usage sections in the menu.\",\n                        binding: self.$settings.showOptionalCreditsAndExtraUsage)\n                    PreferenceToggleRow(\n                        title: \"Show all token accounts\",\n                        subtitle: \"Stack token accounts in the menu (otherwise show an account switcher bar).\",\n                        binding: self.$settings.showAllTokenAccountsInMenu)\n                    self.overviewProviderSelector\n                }\n            }\n            .frame(maxWidth: .infinity, alignment: .leading)\n            .padding(.horizontal, 20)\n            .padding(.vertical, 12)\n            .onAppear {\n                self.reconcileOverviewSelection()\n            }\n            .onChange(of: self.settings.mergeIcons) { _, isEnabled in\n                guard isEnabled else {\n                    self.isOverviewProviderPopoverPresented = false\n                    return\n                }\n                self.reconcileOverviewSelection()\n            }\n            .onChange(of: self.activeProvidersInOrder) { _, _ in\n                if self.activeProvidersInOrder.isEmpty {\n                    self.isOverviewProviderPopoverPresented = false\n                }\n                self.reconcileOverviewSelection()\n            }\n        }\n    }\n\n    private var overviewProviderSelector: some View {\n        VStack(alignment: .leading, spacing: 6) {\n            HStack(alignment: .center, spacing: 12) {\n                Text(\"Overview tab providers\")\n                    .font(.body)\n                Spacer(minLength: 0)\n                if self.showsOverviewConfigureButton {\n                    Button(\"Configure…\") {\n                        self.isOverviewProviderPopoverPresented = true\n                    }\n                    .offset(y: 1)\n                    .popover(isPresented: self.$isOverviewProviderPopoverPresented, arrowEdge: .bottom) {\n                        self.overviewProviderPopover\n                    }\n                }\n            }\n\n            if !self.settings.mergeIcons {\n                Text(\"Enable Merge Icons to configure Overview tab providers.\")\n                    .font(.footnote)\n                    .foregroundStyle(.tertiary)\n            } else if self.activeProvidersInOrder.isEmpty {\n                Text(\"No enabled providers available for Overview.\")\n                    .font(.footnote)\n                    .foregroundStyle(.tertiary)\n            } else {\n                Text(self.overviewProviderSelectionSummary)\n                    .font(.footnote)\n                    .foregroundStyle(.tertiary)\n                    .lineLimit(2)\n                    .truncationMode(.tail)\n            }\n        }\n    }\n\n    private var overviewProviderPopover: some View {\n        VStack(alignment: .leading, spacing: 10) {\n            Text(\"Choose up to \\(Self.maxOverviewProviders) providers\")\n                .font(.headline)\n            Text(\"Overview rows always follow provider order.\")\n                .font(.footnote)\n                .foregroundStyle(.tertiary)\n\n            ScrollView(.vertical, showsIndicators: true) {\n                VStack(alignment: .leading, spacing: 6) {\n                    ForEach(self.activeProvidersInOrder, id: \\.self) { provider in\n                        Toggle(\n                            isOn: Binding(\n                                get: { self.overviewSelectedProviders.contains(provider) },\n                                set: { shouldSelect in\n                                    self.setOverviewProviderSelection(provider: provider, isSelected: shouldSelect)\n                                })) {\n                            Text(self.providerDisplayName(provider))\n                                .font(.body)\n                        }\n                        .toggleStyle(.checkbox)\n                        .disabled(\n                            !self.overviewSelectedProviders.contains(provider) &&\n                                self.overviewSelectedProviders.count >= Self.maxOverviewProviders)\n                    }\n                }\n            }\n            .frame(maxHeight: 220)\n        }\n        .padding(12)\n        .frame(width: 280)\n    }\n\n    private var activeProvidersInOrder: [UsageProvider] {\n        self.store.enabledProviders()\n    }\n\n    private var overviewSelectedProviders: [UsageProvider] {\n        self.settings.resolvedMergedOverviewProviders(\n            activeProviders: self.activeProvidersInOrder,\n            maxVisibleProviders: Self.maxOverviewProviders)\n    }\n\n    private var showsOverviewConfigureButton: Bool {\n        self.settings.mergeIcons && !self.activeProvidersInOrder.isEmpty\n    }\n\n    private var overviewProviderSelectionSummary: String {\n        let selectedNames = self.overviewSelectedProviders.map(self.providerDisplayName)\n        guard !selectedNames.isEmpty else { return \"No providers selected\" }\n        return selectedNames.joined(separator: \", \")\n    }\n\n    private func providerDisplayName(_ provider: UsageProvider) -> String {\n        ProviderDescriptorRegistry.descriptor(for: provider).metadata.displayName\n    }\n\n    private func setOverviewProviderSelection(provider: UsageProvider, isSelected: Bool) {\n        _ = self.settings.setMergedOverviewProviderSelection(\n            provider: provider,\n            isSelected: isSelected,\n            activeProviders: self.activeProvidersInOrder,\n            maxVisibleProviders: Self.maxOverviewProviders)\n    }\n\n    private func reconcileOverviewSelection() {\n        _ = self.settings.reconcileMergedOverviewSelectedProviders(\n            activeProviders: self.activeProvidersInOrder,\n            maxVisibleProviders: Self.maxOverviewProviders)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/PreferencesGeneralPane.swift",
    "content": "import AppKit\nimport CodexBarCore\nimport SwiftUI\n\n@MainActor\nstruct GeneralPane: View {\n    @Bindable var settings: SettingsStore\n    @Bindable var store: UsageStore\n\n    var body: some View {\n        ScrollView(.vertical, showsIndicators: true) {\n            VStack(alignment: .leading, spacing: 16) {\n                SettingsSection(contentSpacing: 12) {\n                    Text(\"System\")\n                        .font(.caption)\n                        .foregroundStyle(.secondary)\n                        .textCase(.uppercase)\n                    PreferenceToggleRow(\n                        title: \"Start at Login\",\n                        subtitle: \"Automatically opens CodexBar when you start your Mac.\",\n                        binding: self.$settings.launchAtLogin)\n                }\n\n                Divider()\n\n                SettingsSection(contentSpacing: 12) {\n                    Text(\"Usage\")\n                        .font(.caption)\n                        .foregroundStyle(.secondary)\n                        .textCase(.uppercase)\n\n                    VStack(alignment: .leading, spacing: 10) {\n                        VStack(alignment: .leading, spacing: 4) {\n                            Toggle(isOn: self.$settings.costUsageEnabled) {\n                                Text(\"Show cost summary\")\n                                    .font(.body)\n                            }\n                            .toggleStyle(.checkbox)\n\n                            Text(\"Reads local usage logs. Shows today + last 30 days cost in the menu.\")\n                                .font(.footnote)\n                                .foregroundStyle(.tertiary)\n                                .fixedSize(horizontal: false, vertical: true)\n\n                            if self.settings.costUsageEnabled {\n                                Text(\"Auto-refresh: hourly · Timeout: 10m\")\n                                    .font(.footnote)\n                                    .foregroundStyle(.tertiary)\n\n                                self.costStatusLine(provider: .claude)\n                                self.costStatusLine(provider: .codex)\n                            }\n                        }\n                    }\n                }\n\n                Divider()\n\n                SettingsSection(contentSpacing: 12) {\n                    Text(\"Automation\")\n                        .font(.caption)\n                        .foregroundStyle(.secondary)\n                        .textCase(.uppercase)\n                    VStack(alignment: .leading, spacing: 6) {\n                        HStack(alignment: .top, spacing: 12) {\n                            VStack(alignment: .leading, spacing: 4) {\n                                Text(\"Refresh cadence\")\n                                    .font(.body)\n                                Text(\"How often CodexBar polls providers in the background.\")\n                                    .font(.footnote)\n                                    .foregroundStyle(.tertiary)\n                            }\n                            Spacer()\n                            Picker(\"Refresh cadence\", selection: self.$settings.refreshFrequency) {\n                                ForEach(RefreshFrequency.allCases) { option in\n                                    Text(option.label).tag(option)\n                                }\n                            }\n                            .labelsHidden()\n                            .pickerStyle(.menu)\n                            .frame(maxWidth: 200)\n                        }\n                        if self.settings.refreshFrequency == .manual {\n                            Text(\"Auto-refresh is off; use the menu's Refresh command.\")\n                                .font(.footnote)\n                                .foregroundStyle(.secondary)\n                        }\n                    }\n                    PreferenceToggleRow(\n                        title: \"Check provider status\",\n                        subtitle: \"Polls OpenAI/Claude status pages and Google Workspace for \" +\n                            \"Gemini/Antigravity, surfacing incidents in the icon and menu.\",\n                        binding: self.$settings.statusChecksEnabled)\n                    PreferenceToggleRow(\n                        title: \"Session quota notifications\",\n                        subtitle: \"Notifies when the 5-hour session quota hits 0% and when it becomes \" +\n                            \"available again.\",\n                        binding: self.$settings.sessionQuotaNotificationsEnabled)\n                }\n\n                Divider()\n\n                SettingsSection(contentSpacing: 12) {\n                    HStack {\n                        Spacer()\n                        Button(\"Quit CodexBar\") { NSApp.terminate(nil) }\n                            .buttonStyle(.borderedProminent)\n                            .controlSize(.large)\n                    }\n                }\n            }\n            .frame(maxWidth: .infinity, alignment: .leading)\n            .padding(.horizontal, 20)\n            .padding(.vertical, 12)\n        }\n    }\n\n    private func costStatusLine(provider: UsageProvider) -> some View {\n        let name = ProviderDescriptorRegistry.descriptor(for: provider).metadata.displayName\n\n        guard provider == .claude || provider == .codex else {\n            return Text(\"\\(name): unsupported\")\n                .font(.footnote)\n                .foregroundStyle(.tertiary)\n        }\n\n        if self.store.isTokenRefreshInFlight(for: provider) {\n            let elapsed: String = {\n                guard let startedAt = self.store.tokenLastAttemptAt(for: provider) else { return \"\" }\n                let seconds = max(0, Date().timeIntervalSince(startedAt))\n                let formatter = DateComponentsFormatter()\n                formatter.allowedUnits = seconds < 60 ? [.second] : [.minute, .second]\n                formatter.unitsStyle = .abbreviated\n                return formatter.string(from: seconds).map { \" (\\($0))\" } ?? \"\"\n            }()\n            return Text(\"\\(name): fetching…\\(elapsed)\")\n                .font(.footnote)\n                .foregroundStyle(.tertiary)\n        }\n        if let snapshot = self.store.tokenSnapshot(for: provider) {\n            let updated = UsageFormatter.updatedString(from: snapshot.updatedAt)\n            let cost = snapshot.last30DaysCostUSD.map { UsageFormatter.usdString($0) } ?? \"—\"\n            return Text(\"\\(name): \\(updated) · 30d \\(cost)\")\n                .font(.footnote)\n                .foregroundStyle(.tertiary)\n        }\n        if let error = self.store.tokenError(for: provider), !error.isEmpty {\n            let truncated = UsageFormatter.truncatedSingleLine(error, max: 120)\n            return Text(\"\\(name): \\(truncated)\")\n                .font(.footnote)\n                .foregroundStyle(.tertiary)\n        }\n        if let lastAttempt = self.store.tokenLastAttemptAt(for: provider) {\n            let rel = RelativeDateTimeFormatter()\n            rel.unitsStyle = .abbreviated\n            let when = rel.localizedString(for: lastAttempt, relativeTo: Date())\n            return Text(\"\\(name): last attempt \\(when)\")\n                .font(.footnote)\n                .foregroundStyle(.tertiary)\n        }\n        return Text(\"\\(name): no data yet\")\n            .font(.footnote)\n            .foregroundStyle(.tertiary)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/PreferencesProviderDetailView.swift",
    "content": "import CodexBarCore\nimport SwiftUI\n\n@MainActor\nstruct ProviderDetailView: View {\n    let provider: UsageProvider\n    @Bindable var store: UsageStore\n    @Binding var isEnabled: Bool\n    let subtitle: String\n    let model: UsageMenuCardView.Model\n    let settingsPickers: [ProviderSettingsPickerDescriptor]\n    let settingsToggles: [ProviderSettingsToggleDescriptor]\n    let settingsFields: [ProviderSettingsFieldDescriptor]\n    let settingsTokenAccounts: ProviderSettingsTokenAccountsDescriptor?\n    let errorDisplay: ProviderErrorDisplay?\n    @Binding var isErrorExpanded: Bool\n    let onCopyError: (String) -> Void\n    let onRefresh: () -> Void\n\n    static func metricTitle(provider: UsageProvider, metric: UsageMenuCardView.Model.Metric) -> String {\n        UsageMenuCardView.popupMetricTitle(provider: provider, metric: metric)\n    }\n\n    static func planRow(provider: UsageProvider, planText: String?) -> (label: String, value: String)? {\n        guard let rawPlan = planText?.trimmingCharacters(in: .whitespacesAndNewlines),\n              !rawPlan.isEmpty\n        else {\n            return nil\n        }\n        guard provider == .openrouter else {\n            return (label: \"Plan\", value: rawPlan)\n        }\n\n        let prefix = \"Balance:\"\n        if rawPlan.hasPrefix(prefix) {\n            let valueStart = rawPlan.index(rawPlan.startIndex, offsetBy: prefix.count)\n            let trimmedValue = rawPlan[valueStart...].trimmingCharacters(in: .whitespacesAndNewlines)\n            if !trimmedValue.isEmpty {\n                return (label: \"Balance\", value: trimmedValue)\n            }\n        }\n        return (label: \"Balance\", value: rawPlan)\n    }\n\n    var body: some View {\n        ScrollView {\n            VStack(alignment: .leading, spacing: 16) {\n                let labelWidth = self.detailLabelWidth\n                ProviderDetailHeaderView(\n                    provider: self.provider,\n                    store: self.store,\n                    isEnabled: self.$isEnabled,\n                    subtitle: self.subtitle,\n                    model: self.model,\n                    labelWidth: labelWidth,\n                    onRefresh: self.onRefresh)\n\n                ProviderMetricsInlineView(\n                    provider: self.provider,\n                    model: self.model,\n                    isEnabled: self.isEnabled,\n                    labelWidth: labelWidth)\n\n                if let errorDisplay {\n                    ProviderErrorView(\n                        title: \"Last \\(self.store.metadata(for: self.provider).displayName) fetch failed:\",\n                        display: errorDisplay,\n                        isExpanded: self.$isErrorExpanded,\n                        onCopy: { self.onCopyError(errorDisplay.full) })\n                }\n\n                if self.hasSettings {\n                    ProviderSettingsSection(title: \"Settings\") {\n                        ForEach(self.settingsPickers) { picker in\n                            ProviderSettingsPickerRowView(picker: picker)\n                        }\n                        if let tokenAccounts = self.settingsTokenAccounts,\n                           tokenAccounts.isVisible?() ?? true\n                        {\n                            ProviderSettingsTokenAccountsRowView(descriptor: tokenAccounts)\n                        }\n                        ForEach(self.settingsFields) { field in\n                            ProviderSettingsFieldRowView(field: field)\n                        }\n                    }\n                }\n\n                if !self.settingsToggles.isEmpty {\n                    ProviderSettingsSection(title: \"Options\") {\n                        ForEach(self.settingsToggles) { toggle in\n                            ProviderSettingsToggleRowView(toggle: toggle)\n                        }\n                    }\n                }\n            }\n            .frame(maxWidth: ProviderSettingsMetrics.detailMaxWidth, alignment: .leading)\n            .padding(.vertical, 12)\n            .padding(.horizontal, 8)\n        }\n        .frame(maxWidth: .infinity, alignment: .leading)\n    }\n\n    private var hasSettings: Bool {\n        !self.settingsPickers.isEmpty ||\n            !self.settingsFields.isEmpty ||\n            self.settingsTokenAccounts != nil\n    }\n\n    private var detailLabelWidth: CGFloat {\n        var infoLabels = [\"State\", \"Source\", \"Version\", \"Updated\"]\n        if self.store.status(for: self.provider) != nil {\n            infoLabels.append(\"Status\")\n        }\n        if !self.model.email.isEmpty {\n            infoLabels.append(\"Account\")\n        }\n        if let planRow = Self.planRow(provider: self.provider, planText: self.model.planText) {\n            infoLabels.append(planRow.label)\n        }\n\n        var metricLabels = self.model.metrics.map { metric in\n            Self.metricTitle(provider: self.provider, metric: metric)\n        }\n        if self.model.creditsText != nil {\n            metricLabels.append(\"Credits\")\n        }\n        if let providerCost = self.model.providerCost {\n            metricLabels.append(providerCost.title)\n        }\n        if self.model.tokenUsage != nil {\n            metricLabels.append(\"Cost\")\n        }\n\n        let infoWidth = ProviderSettingsMetrics.labelWidth(\n            for: infoLabels,\n            font: ProviderSettingsMetrics.infoLabelFont())\n        let metricWidth = ProviderSettingsMetrics.labelWidth(\n            for: metricLabels,\n            font: ProviderSettingsMetrics.metricLabelFont())\n        return max(infoWidth, metricWidth)\n    }\n}\n\n@MainActor\nprivate struct ProviderDetailHeaderView: View {\n    let provider: UsageProvider\n    @Bindable var store: UsageStore\n    @Binding var isEnabled: Bool\n    let subtitle: String\n    let model: UsageMenuCardView.Model\n    let labelWidth: CGFloat\n    let onRefresh: () -> Void\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 12) {\n            HStack(alignment: .center, spacing: 12) {\n                ProviderDetailBrandIcon(provider: self.provider)\n\n                VStack(alignment: .leading, spacing: 4) {\n                    Text(self.store.metadata(for: self.provider).displayName)\n                        .font(.title3.weight(.semibold))\n\n                    Text(self.detailSubtitle)\n                        .font(.footnote)\n                        .foregroundStyle(.secondary)\n                }\n\n                Spacer(minLength: 12)\n\n                Button {\n                    self.onRefresh()\n                } label: {\n                    Image(systemName: \"arrow.clockwise\")\n                }\n                .buttonStyle(.bordered)\n                .controlSize(.small)\n                .help(\"Refresh\")\n\n                Toggle(\"\", isOn: self.$isEnabled)\n                    .labelsHidden()\n                    .toggleStyle(.switch)\n                    .controlSize(.small)\n            }\n\n            ProviderDetailInfoGrid(\n                provider: self.provider,\n                store: self.store,\n                isEnabled: self.isEnabled,\n                model: self.model,\n                labelWidth: self.labelWidth)\n        }\n    }\n\n    private var detailSubtitle: String {\n        let lines = self.subtitle.split(separator: \"\\n\", omittingEmptySubsequences: false)\n        guard lines.count >= 2 else { return self.subtitle }\n        let first = lines[0]\n        let rest = lines.dropFirst().joined(separator: \"\\n\")\n        let tail = rest.trimmingCharacters(in: .whitespacesAndNewlines)\n        if tail.isEmpty { return String(first) }\n        return \"\\(first) • \\(tail)\"\n    }\n}\n\n@MainActor\nprivate struct ProviderDetailBrandIcon: View {\n    let provider: UsageProvider\n\n    var body: some View {\n        if let brand = ProviderBrandIcon.image(for: self.provider) {\n            Image(nsImage: brand)\n                .resizable()\n                .scaledToFit()\n                .frame(width: 28, height: 28)\n                .foregroundStyle(.secondary)\n                .accessibilityHidden(true)\n        } else {\n            Image(systemName: \"circle.dotted\")\n                .font(.system(size: 24, weight: .regular))\n                .foregroundStyle(.secondary)\n                .accessibilityHidden(true)\n        }\n    }\n}\n\n@MainActor\nprivate struct ProviderDetailInfoGrid: View {\n    let provider: UsageProvider\n    @Bindable var store: UsageStore\n    let isEnabled: Bool\n    let model: UsageMenuCardView.Model\n    let labelWidth: CGFloat\n\n    var body: some View {\n        let status = self.store.status(for: self.provider)\n        let source = self.store.sourceLabel(for: self.provider)\n        let version = self.store.version(for: self.provider) ?? \"not detected\"\n        let updated = self.updatedText\n        let email = self.model.email\n        let enabledText = self.isEnabled ? \"Enabled\" : \"Disabled\"\n\n        Grid(alignment: .leading, horizontalSpacing: 12, verticalSpacing: 6) {\n            ProviderDetailInfoRow(label: \"State\", value: enabledText, labelWidth: self.labelWidth)\n            ProviderDetailInfoRow(label: \"Source\", value: source, labelWidth: self.labelWidth)\n            ProviderDetailInfoRow(label: \"Version\", value: version, labelWidth: self.labelWidth)\n            ProviderDetailInfoRow(label: \"Updated\", value: updated, labelWidth: self.labelWidth)\n\n            if let status {\n                ProviderDetailInfoRow(\n                    label: \"Status\",\n                    value: status.description ?? status.indicator.label,\n                    labelWidth: self.labelWidth)\n            }\n\n            if !email.isEmpty {\n                ProviderDetailInfoRow(label: \"Account\", value: email, labelWidth: self.labelWidth)\n            }\n\n            if let planRow = ProviderDetailView.planRow(provider: self.provider, planText: self.model.planText) {\n                ProviderDetailInfoRow(label: planRow.label, value: planRow.value, labelWidth: self.labelWidth)\n            }\n        }\n        .font(.footnote)\n        .foregroundStyle(.secondary)\n    }\n\n    private var updatedText: String {\n        if let updated = self.store.snapshot(for: self.provider)?.updatedAt {\n            return UsageFormatter.updatedString(from: updated)\n        }\n        if self.store.refreshingProviders.contains(self.provider) {\n            return \"Refreshing\"\n        }\n        return \"Not fetched yet\"\n    }\n}\n\nprivate struct ProviderDetailInfoRow: View {\n    let label: String\n    let value: String\n    let labelWidth: CGFloat\n\n    var body: some View {\n        GridRow {\n            Text(self.label)\n                .frame(width: self.labelWidth, alignment: .leading)\n            Text(self.value)\n                .lineLimit(2)\n        }\n    }\n}\n\n@MainActor\nstruct ProviderMetricsInlineView: View {\n    let provider: UsageProvider\n    let model: UsageMenuCardView.Model\n    let isEnabled: Bool\n    let labelWidth: CGFloat\n\n    var body: some View {\n        let hasMetrics = !self.model.metrics.isEmpty\n        let hasUsageNotes = !self.model.usageNotes.isEmpty\n        let hasCredits = self.model.creditsText != nil\n        let hasProviderCost = self.model.providerCost != nil\n        let hasTokenUsage = self.model.tokenUsage != nil\n        ProviderSettingsSection(\n            title: \"Usage\",\n            spacing: 8,\n            verticalPadding: 6,\n            horizontalPadding: 0)\n        {\n            if !hasMetrics, !hasUsageNotes, !hasProviderCost, !hasCredits, !hasTokenUsage {\n                Text(self.placeholderText)\n                    .font(.footnote)\n                    .foregroundStyle(.secondary)\n            } else {\n                ForEach(self.model.metrics, id: \\.id) { metric in\n                    ProviderMetricInlineRow(\n                        metric: metric,\n                        title: ProviderDetailView.metricTitle(provider: self.provider, metric: metric),\n                        progressColor: self.model.progressColor,\n                        labelWidth: self.labelWidth)\n                }\n\n                if hasUsageNotes {\n                    ProviderUsageNotesInlineView(\n                        notes: self.model.usageNotes,\n                        labelWidth: self.labelWidth,\n                        alignsWithMetricContent: hasMetrics)\n                }\n\n                if let credits = self.model.creditsText {\n                    ProviderMetricInlineTextRow(\n                        title: \"Credits\",\n                        value: credits,\n                        labelWidth: self.labelWidth)\n                }\n\n                if let providerCost = self.model.providerCost {\n                    ProviderMetricInlineCostRow(\n                        section: providerCost,\n                        progressColor: self.model.progressColor,\n                        labelWidth: self.labelWidth)\n                }\n\n                if let tokenUsage = self.model.tokenUsage {\n                    ProviderMetricInlineTextRow(\n                        title: \"Cost\",\n                        value: tokenUsage.sessionLine,\n                        labelWidth: self.labelWidth)\n                    ProviderMetricInlineTextRow(\n                        title: \"\",\n                        value: tokenUsage.monthLine,\n                        labelWidth: self.labelWidth)\n                }\n            }\n        }\n    }\n\n    private var placeholderText: String {\n        if !self.isEnabled {\n            return \"Disabled — no recent data\"\n        }\n        return self.model.placeholder ?? \"No usage yet\"\n    }\n}\n\nprivate struct ProviderMetricInlineRow: View {\n    let metric: UsageMenuCardView.Model.Metric\n    let title: String\n    let progressColor: Color\n    let labelWidth: CGFloat\n\n    var body: some View {\n        HStack(alignment: .top, spacing: 10) {\n            Text(self.title)\n                .font(.subheadline.weight(.semibold))\n                .lineLimit(1)\n                .frame(width: self.labelWidth, alignment: .leading)\n\n            VStack(alignment: .leading, spacing: 4) {\n                UsageProgressBar(\n                    percent: self.metric.percent,\n                    tint: self.progressColor,\n                    accessibilityLabel: self.metric.percentStyle.accessibilityLabel,\n                    pacePercent: self.metric.pacePercent,\n                    paceOnTop: self.metric.paceOnTop)\n                    .frame(minWidth: ProviderSettingsMetrics.metricBarWidth, maxWidth: .infinity)\n\n                HStack(alignment: .firstTextBaseline, spacing: 8) {\n                    Text(self.metric.percentLabel)\n                        .font(.footnote)\n                        .foregroundStyle(.secondary)\n                        .monospacedDigit()\n                    Spacer(minLength: 8)\n                    if let resetText = self.metric.resetText, !resetText.isEmpty {\n                        Text(resetText)\n                            .font(.footnote)\n                            .foregroundStyle(.secondary)\n                    }\n                }\n\n                let hasLeftDetail = self.metric.detailLeftText?.isEmpty == false\n                let hasRightDetail = self.metric.detailRightText?.isEmpty == false\n                if hasLeftDetail || hasRightDetail {\n                    HStack(alignment: .firstTextBaseline, spacing: 8) {\n                        if let leftDetail = self.metric.detailLeftText, !leftDetail.isEmpty {\n                            Text(leftDetail)\n                                .font(.footnote)\n                                .foregroundStyle(.secondary)\n                        }\n                        Spacer(minLength: 8)\n                        if let rightDetail = self.metric.detailRightText, !rightDetail.isEmpty {\n                            Text(rightDetail)\n                                .font(.footnote)\n                                .foregroundStyle(.secondary)\n                        }\n                    }\n                }\n\n                if let detail = self.detailText, !detail.isEmpty {\n                    Text(detail)\n                        .font(.footnote)\n                        .foregroundStyle(.tertiary)\n                }\n            }\n            .frame(maxWidth: .infinity, alignment: .leading)\n        }\n        .padding(.vertical, 2)\n    }\n\n    private var detailText: String? {\n        guard let detailText = self.metric.detailText, !detailText.isEmpty else { return nil }\n        return detailText\n    }\n}\n\nprivate struct ProviderUsageNotesInlineView: View {\n    let notes: [String]\n    let labelWidth: CGFloat\n    let alignsWithMetricContent: Bool\n\n    var body: some View {\n        HStack(alignment: .top, spacing: 10) {\n            if self.alignsWithMetricContent {\n                Spacer()\n                    .frame(width: self.labelWidth)\n            }\n            VStack(alignment: .leading, spacing: 4) {\n                ForEach(Array(self.notes.enumerated()), id: \\.offset) { _, note in\n                    Text(note)\n                        .font(.footnote)\n                        .foregroundStyle(.secondary)\n                        .lineLimit(2)\n                        .fixedSize(horizontal: false, vertical: true)\n                }\n            }\n            .frame(maxWidth: .infinity, alignment: .leading)\n        }\n        .padding(.vertical, 2)\n    }\n}\n\nprivate struct ProviderMetricInlineTextRow: View {\n    let title: String\n    let value: String\n    let labelWidth: CGFloat\n\n    var body: some View {\n        HStack(alignment: .firstTextBaseline, spacing: 12) {\n            Text(self.title)\n                .font(.subheadline.weight(.semibold))\n                .frame(width: self.labelWidth, alignment: .leading)\n\n            Text(self.value)\n                .font(.footnote)\n                .foregroundStyle(.secondary)\n\n            Spacer(minLength: 0)\n        }\n        .padding(.vertical, 1)\n    }\n}\n\nprivate struct ProviderMetricInlineCostRow: View {\n    let section: UsageMenuCardView.Model.ProviderCostSection\n    let progressColor: Color\n    let labelWidth: CGFloat\n\n    var body: some View {\n        HStack(alignment: .top, spacing: 10) {\n            Text(self.section.title)\n                .font(.subheadline.weight(.semibold))\n                .frame(width: self.labelWidth, alignment: .leading)\n\n            VStack(alignment: .leading, spacing: 4) {\n                UsageProgressBar(\n                    percent: self.section.percentUsed,\n                    tint: self.progressColor,\n                    accessibilityLabel: \"Usage used\")\n                    .frame(minWidth: ProviderSettingsMetrics.metricBarWidth, maxWidth: .infinity)\n\n                HStack(alignment: .firstTextBaseline, spacing: 8) {\n                    Text(String(format: \"%.0f%% used\", self.section.percentUsed))\n                        .font(.footnote)\n                        .foregroundStyle(.secondary)\n                        .monospacedDigit()\n                    Spacer(minLength: 8)\n                    Text(self.section.spendLine)\n                        .font(.footnote)\n                        .foregroundStyle(.secondary)\n                }\n            }\n\n            Spacer(minLength: 0)\n        }\n        .padding(.vertical, 2)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/PreferencesProviderErrorView.swift",
    "content": "import SwiftUI\n\nstruct ProviderErrorDisplay {\n    let preview: String\n    let full: String\n}\n\n@MainActor\nstruct ProviderErrorView: View {\n    let title: String\n    let display: ProviderErrorDisplay\n    @Binding var isExpanded: Bool\n    let onCopy: () -> Void\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 6) {\n            HStack(alignment: .firstTextBaseline, spacing: 8) {\n                Text(self.title)\n                    .font(.footnote.weight(.semibold))\n                    .foregroundStyle(.secondary)\n                Spacer()\n                Button {\n                    self.onCopy()\n                } label: {\n                    Image(systemName: \"doc.on.doc\")\n                }\n                .buttonStyle(.plain)\n                .foregroundStyle(.secondary)\n                .help(\"Copy error\")\n            }\n\n            Text(self.display.preview)\n                .font(.footnote)\n                .foregroundStyle(.secondary)\n                .lineLimit(3)\n                .fixedSize(horizontal: false, vertical: true)\n\n            if self.display.preview != self.display.full {\n                Button(self.isExpanded ? \"Hide details\" : \"Show details\") { self.isExpanded.toggle() }\n                    .buttonStyle(.link)\n                    .font(.footnote)\n            }\n\n            if self.isExpanded {\n                Text(self.display.full)\n                    .font(.footnote)\n                    .textSelection(.enabled)\n                    .fixedSize(horizontal: false, vertical: true)\n            }\n        }\n        .padding(.leading, 2)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/PreferencesProviderSettingsMetrics.swift",
    "content": "import AppKit\nimport SwiftUI\n\nenum ProviderSettingsMetrics {\n    static let rowSpacing: CGFloat = 12\n    static let rowInsets = EdgeInsets(top: 6, leading: 0, bottom: 6, trailing: 0)\n    static let dividerBottomInset: CGFloat = 8\n    static let listTopPadding: CGFloat = 12\n    static let checkboxSize: CGFloat = 18\n    static let iconSize: CGFloat = 18\n    static let reorderHandleSize: CGFloat = 12\n    static let reorderDotSize: CGFloat = 2\n    static let reorderDotSpacing: CGFloat = 3\n    static let pickerLabelWidth: CGFloat = 92\n    static let sidebarWidth: CGFloat = 240\n    static let sidebarCornerRadius: CGFloat = 12\n    static let sidebarSubtitleHeight: CGFloat = {\n        let font = NSFont.systemFont(ofSize: NSFont.smallSystemFontSize)\n        let layout = NSLayoutManager()\n        return ceil(layout.defaultLineHeight(for: font) * 2)\n    }()\n\n    static let detailMaxWidth: CGFloat = 640\n    static let metricLabelWidth: CGFloat = 120\n    static let metricBarWidth: CGFloat = 220\n\n    static func labelWidth(for labels: [String], font: NSFont, minimum: CGFloat = 0) -> CGFloat {\n        let maxWidth = labels\n            .filter { !$0.isEmpty }\n            .map { ($0 as NSString).size(withAttributes: [.font: font]).width }\n            .max() ?? 0\n        return max(minimum, ceil(maxWidth))\n    }\n\n    static func metricLabelFont() -> NSFont {\n        let baseSize = NSFont.preferredFont(forTextStyle: .subheadline).pointSize\n        return NSFont.systemFont(ofSize: baseSize, weight: .semibold)\n    }\n\n    static func infoLabelFont() -> NSFont {\n        NSFont.preferredFont(forTextStyle: .footnote)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/PreferencesProviderSettingsRows.swift",
    "content": "import SwiftUI\n\nstruct ProviderSettingsSection<Content: View>: View {\n    let title: String\n    let spacing: CGFloat\n    let verticalPadding: CGFloat\n    let horizontalPadding: CGFloat\n    @ViewBuilder let content: () -> Content\n\n    init(\n        title: String,\n        spacing: CGFloat = 12,\n        verticalPadding: CGFloat = 10,\n        horizontalPadding: CGFloat = 4,\n        @ViewBuilder content: @escaping () -> Content)\n    {\n        self.title = title\n        self.spacing = spacing\n        self.verticalPadding = verticalPadding\n        self.horizontalPadding = horizontalPadding\n        self.content = content\n    }\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: self.spacing) {\n            Text(self.title)\n                .font(.headline)\n            self.content()\n        }\n        .frame(maxWidth: .infinity, alignment: .leading)\n        .padding(.vertical, self.verticalPadding)\n        .padding(.horizontal, self.horizontalPadding)\n    }\n}\n\n@MainActor\nstruct ProviderSettingsToggleRowView: View {\n    let toggle: ProviderSettingsToggleDescriptor\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 8) {\n            HStack(alignment: .firstTextBaseline, spacing: 12) {\n                VStack(alignment: .leading, spacing: 4) {\n                    Text(self.toggle.title)\n                        .font(.subheadline.weight(.semibold))\n                    Text(self.toggle.subtitle)\n                        .font(.footnote)\n                        .foregroundStyle(.secondary)\n                        .fixedSize(horizontal: false, vertical: true)\n                }\n                Spacer(minLength: 8)\n                Toggle(\"\", isOn: self.toggle.binding)\n                    .labelsHidden()\n                    .toggleStyle(.switch)\n            }\n\n            if self.toggle.binding.wrappedValue {\n                if let status = self.toggle.statusText?(), !status.isEmpty {\n                    Text(status)\n                        .font(.footnote)\n                        .foregroundStyle(.secondary)\n                        .lineLimit(4)\n                        .fixedSize(horizontal: false, vertical: true)\n                }\n\n                let actions = self.toggle.actions.filter { $0.isVisible?() ?? true }\n                if !actions.isEmpty {\n                    HStack(spacing: 10) {\n                        ForEach(actions) { action in\n                            Button(action.title) {\n                                Task { @MainActor in\n                                    await action.perform()\n                                }\n                            }\n                            .applyProviderSettingsButtonStyle(action.style)\n                            .controlSize(.small)\n                        }\n                    }\n                }\n            }\n        }\n        .onChange(of: self.toggle.binding.wrappedValue) { _, enabled in\n            guard let onChange = self.toggle.onChange else { return }\n            Task { @MainActor in\n                await onChange(enabled)\n            }\n        }\n        .task(id: self.toggle.binding.wrappedValue) {\n            guard self.toggle.binding.wrappedValue else { return }\n            guard let onAppear = self.toggle.onAppearWhenEnabled else { return }\n            await onAppear()\n        }\n    }\n}\n\n@MainActor\nstruct ProviderSettingsPickerRowView: View {\n    let picker: ProviderSettingsPickerDescriptor\n\n    var body: some View {\n        let isEnabled = self.picker.isEnabled?() ?? true\n        VStack(alignment: .leading, spacing: 6) {\n            HStack(alignment: .firstTextBaseline, spacing: 10) {\n                Text(self.picker.title)\n                    .font(.subheadline.weight(.semibold))\n                    .frame(width: ProviderSettingsMetrics.pickerLabelWidth, alignment: .leading)\n\n                Picker(\"\", selection: self.picker.binding) {\n                    ForEach(self.picker.options) { option in\n                        Text(option.title).tag(option.id)\n                    }\n                }\n                .labelsHidden()\n                .pickerStyle(.menu)\n                .controlSize(.small)\n\n                if let trailingText = self.picker.trailingText?(), !trailingText.isEmpty {\n                    Text(trailingText)\n                        .font(.footnote)\n                        .foregroundStyle(.secondary)\n                        .lineLimit(1)\n                        .truncationMode(.tail)\n                        .padding(.leading, 4)\n                }\n\n                Spacer(minLength: 0)\n            }\n\n            let subtitle = self.picker.dynamicSubtitle?() ?? self.picker.subtitle\n            if !subtitle.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {\n                Text(subtitle)\n                    .font(.footnote)\n                    .foregroundStyle(.secondary)\n                    .fixedSize(horizontal: false, vertical: true)\n            }\n        }\n        .disabled(!isEnabled)\n        .onChange(of: self.picker.binding.wrappedValue) { _, selection in\n            guard let onChange = self.picker.onChange else { return }\n            Task { @MainActor in\n                await onChange(selection)\n            }\n        }\n    }\n}\n\n@MainActor\nstruct ProviderSettingsFieldRowView: View {\n    let field: ProviderSettingsFieldDescriptor\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 8) {\n            let trimmedTitle = self.field.title.trimmingCharacters(in: .whitespacesAndNewlines)\n            let trimmedSubtitle = self.field.subtitle.trimmingCharacters(in: .whitespacesAndNewlines)\n            let hasHeader = !trimmedTitle.isEmpty || !trimmedSubtitle.isEmpty\n\n            if hasHeader {\n                VStack(alignment: .leading, spacing: 4) {\n                    if !trimmedTitle.isEmpty {\n                        Text(trimmedTitle)\n                            .font(.subheadline.weight(.semibold))\n                    }\n                    if !trimmedSubtitle.isEmpty {\n                        Text(trimmedSubtitle)\n                            .font(.footnote)\n                            .foregroundStyle(.secondary)\n                            .fixedSize(horizontal: false, vertical: true)\n                    }\n                }\n            }\n\n            switch self.field.kind {\n            case .plain:\n                TextField(self.field.placeholder ?? \"\", text: self.field.binding)\n                    .textFieldStyle(.roundedBorder)\n                    .font(.footnote)\n                    .onTapGesture { self.field.onActivate?() }\n            case .secure:\n                SecureField(self.field.placeholder ?? \"\", text: self.field.binding)\n                    .textFieldStyle(.roundedBorder)\n                    .font(.footnote)\n                    .onTapGesture { self.field.onActivate?() }\n            }\n\n            let actions = self.field.actions.filter { $0.isVisible?() ?? true }\n            if !actions.isEmpty {\n                HStack(spacing: 10) {\n                    ForEach(actions) { action in\n                        Button(action.title) {\n                            Task { @MainActor in\n                                await action.perform()\n                            }\n                        }\n                        .applyProviderSettingsButtonStyle(action.style)\n                        .controlSize(.small)\n                    }\n                }\n            }\n        }\n    }\n}\n\n@MainActor\nstruct ProviderSettingsTokenAccountsRowView: View {\n    let descriptor: ProviderSettingsTokenAccountsDescriptor\n    @State private var newLabel: String = \"\"\n    @State private var newToken: String = \"\"\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 8) {\n            Text(self.descriptor.title)\n                .font(.subheadline.weight(.semibold))\n\n            if !self.descriptor.subtitle.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {\n                Text(self.descriptor.subtitle)\n                    .font(.footnote)\n                    .foregroundStyle(.secondary)\n                    .fixedSize(horizontal: false, vertical: true)\n            }\n\n            let accounts = self.descriptor.accounts()\n            if accounts.isEmpty {\n                Text(\"No token accounts yet.\")\n                    .font(.footnote)\n                    .foregroundStyle(.secondary)\n            } else {\n                let selectedIndex = min(self.descriptor.activeIndex(), max(0, accounts.count - 1))\n                Picker(\"\", selection: Binding(\n                    get: { selectedIndex },\n                    set: { index in self.descriptor.setActiveIndex(index) }))\n                {\n                    ForEach(Array(accounts.enumerated()), id: \\.offset) { index, account in\n                        Text(account.displayName).tag(index)\n                    }\n                }\n                .labelsHidden()\n                .pickerStyle(.menu)\n                .controlSize(.small)\n\n                Button(\"Remove selected account\") {\n                    let account = accounts[selectedIndex]\n                    self.descriptor.removeAccount(account.id)\n                }\n                .buttonStyle(.bordered)\n                .controlSize(.small)\n            }\n\n            HStack(spacing: 8) {\n                TextField(\"Label\", text: self.$newLabel)\n                    .textFieldStyle(.roundedBorder)\n                    .font(.footnote)\n                SecureField(self.descriptor.placeholder, text: self.$newToken)\n                    .textFieldStyle(.roundedBorder)\n                    .font(.footnote)\n                Button(\"Add\") {\n                    let label = self.newLabel.trimmingCharacters(in: .whitespacesAndNewlines)\n                    let token = self.newToken.trimmingCharacters(in: .whitespacesAndNewlines)\n                    guard !label.isEmpty, !token.isEmpty else { return }\n                    self.descriptor.addAccount(label, token)\n                    self.newLabel = \"\"\n                    self.newToken = \"\"\n                }\n                .buttonStyle(.bordered)\n                .controlSize(.small)\n                .disabled(self.newLabel.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ||\n                    self.newToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)\n            }\n\n            HStack(spacing: 10) {\n                Button(\"Open token file\") {\n                    self.descriptor.openConfigFile()\n                }\n                .buttonStyle(.link)\n                .controlSize(.small)\n                Button(\"Reload\") {\n                    self.descriptor.reloadFromDisk()\n                }\n                .buttonStyle(.link)\n                .controlSize(.small)\n            }\n        }\n    }\n}\n\nextension View {\n    @ViewBuilder\n    fileprivate func applyProviderSettingsButtonStyle(_ style: ProviderSettingsActionDescriptor.Style) -> some View {\n        switch style {\n        case .bordered:\n            self.buttonStyle(.bordered)\n        case .link:\n            self.buttonStyle(.link)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/PreferencesProviderSidebarView.swift",
    "content": "import CodexBarCore\nimport SwiftUI\nimport UniformTypeIdentifiers\n\n@MainActor\nstruct ProviderSidebarListView: View {\n    let providers: [UsageProvider]\n    @Bindable var store: UsageStore\n    let isEnabled: (UsageProvider) -> Binding<Bool>\n    let subtitle: (UsageProvider) -> String\n    @Binding var selection: UsageProvider?\n    let moveProviders: (IndexSet, Int) -> Void\n    @State private var draggingProvider: UsageProvider?\n\n    var body: some View {\n        List(selection: self.$selection) {\n            ForEach(self.providers, id: \\.self) { provider in\n                ProviderSidebarRowView(\n                    provider: provider,\n                    store: self.store,\n                    isEnabled: self.isEnabled(provider),\n                    subtitle: self.subtitle(provider),\n                    draggingProvider: self.$draggingProvider)\n                    .tag(provider)\n                    .onDrop(\n                        of: [UTType.plainText],\n                        delegate: ProviderSidebarDropDelegate(\n                            item: provider,\n                            providers: self.providers,\n                            dragging: self.$draggingProvider,\n                            moveProviders: self.moveProviders))\n            }\n        }\n        .listStyle(.sidebar)\n        .scrollContentBackground(.hidden)\n        .background(\n            RoundedRectangle(cornerRadius: ProviderSettingsMetrics.sidebarCornerRadius, style: .continuous)\n                .fill(.regularMaterial))\n        .overlay(\n            RoundedRectangle(cornerRadius: ProviderSettingsMetrics.sidebarCornerRadius, style: .continuous)\n                .stroke(Color(nsColor: .separatorColor).opacity(0.7), lineWidth: 1))\n        .clipShape(RoundedRectangle(cornerRadius: ProviderSettingsMetrics.sidebarCornerRadius, style: .continuous))\n        .frame(minWidth: ProviderSettingsMetrics.sidebarWidth, maxWidth: ProviderSettingsMetrics.sidebarWidth)\n    }\n}\n\n@MainActor\nprivate struct ProviderSidebarRowView: View {\n    let provider: UsageProvider\n    @Bindable var store: UsageStore\n    @Binding var isEnabled: Bool\n    let subtitle: String\n    @Binding var draggingProvider: UsageProvider?\n\n    var body: some View {\n        let isRefreshing = self.store.refreshingProviders.contains(self.provider)\n        let showStatus = self.store.statusChecksEnabled\n        let statusText = self.statusText\n\n        HStack(alignment: .center, spacing: 10) {\n            ProviderSidebarReorderHandle()\n                .contentShape(Rectangle())\n                .padding(.vertical, 4)\n                .padding(.horizontal, 2)\n                .help(\"Drag to reorder\")\n                .onDrag {\n                    self.draggingProvider = self.provider\n                    return NSItemProvider(object: self.provider.rawValue as NSString)\n                }\n\n            ProviderSidebarBrandIcon(provider: self.provider)\n\n            VStack(alignment: .leading, spacing: 2) {\n                HStack(spacing: 6) {\n                    Text(self.store.metadata(for: self.provider).displayName)\n                        .font(.subheadline.weight(.semibold))\n                        .foregroundStyle(.primary)\n\n                    if showStatus {\n                        ProviderStatusDot(indicator: self.store.statusIndicator(for: self.provider))\n                    }\n\n                    if isRefreshing {\n                        ProgressView()\n                            .controlSize(.mini)\n                    }\n                }\n                Text(statusText)\n                    .font(.caption)\n                    .foregroundStyle(.secondary)\n                    .lineLimit(2)\n                    .frame(height: ProviderSettingsMetrics.sidebarSubtitleHeight, alignment: .topLeading)\n            }\n\n            Spacer(minLength: 8)\n\n            Toggle(\"\", isOn: self.$isEnabled)\n                .labelsHidden()\n                .toggleStyle(.checkbox)\n                .controlSize(.small)\n        }\n        .contentShape(Rectangle())\n        .padding(.vertical, 2)\n    }\n\n    private var statusText: String {\n        guard !self.isEnabled else { return self.subtitle }\n        let lines = self.subtitle.split(separator: \"\\n\", omittingEmptySubsequences: false)\n        if lines.count >= 2 {\n            let first = lines[0]\n            let rest = lines.dropFirst().joined(separator: \"\\n\")\n            return \"Disabled — \\(first)\\n\\(rest)\"\n        }\n        return \"Disabled — \\(self.subtitle)\"\n    }\n}\n\nprivate struct ProviderSidebarReorderHandle: View {\n    var body: some View {\n        VStack(spacing: ProviderSettingsMetrics.reorderDotSpacing) {\n            ForEach(0..<3, id: \\.self) { _ in\n                HStack(spacing: ProviderSettingsMetrics.reorderDotSpacing) {\n                    Circle()\n                        .frame(\n                            width: ProviderSettingsMetrics.reorderDotSize,\n                            height: ProviderSettingsMetrics.reorderDotSize)\n                    Circle()\n                        .frame(\n                            width: ProviderSettingsMetrics.reorderDotSize,\n                            height: ProviderSettingsMetrics.reorderDotSize)\n                }\n            }\n        }\n        .frame(\n            width: ProviderSettingsMetrics.reorderHandleSize,\n            height: ProviderSettingsMetrics.reorderHandleSize)\n        .foregroundStyle(.tertiary)\n        .accessibilityLabel(\"Reorder\")\n    }\n}\n\n@MainActor\nprivate struct ProviderSidebarBrandIcon: View {\n    let provider: UsageProvider\n\n    var body: some View {\n        if let brand = ProviderBrandIcon.image(for: self.provider) {\n            Image(nsImage: brand)\n                .resizable()\n                .scaledToFit()\n                .frame(width: ProviderSettingsMetrics.iconSize, height: ProviderSettingsMetrics.iconSize)\n                .foregroundStyle(.secondary)\n                .accessibilityHidden(true)\n        } else {\n            Image(systemName: \"circle.dotted\")\n                .font(.system(size: ProviderSettingsMetrics.iconSize, weight: .regular))\n                .foregroundStyle(.secondary)\n                .accessibilityHidden(true)\n        }\n    }\n}\n\nprivate struct ProviderSidebarDropDelegate: DropDelegate {\n    let item: UsageProvider\n    let providers: [UsageProvider]\n    @Binding var dragging: UsageProvider?\n    let moveProviders: (IndexSet, Int) -> Void\n\n    func dropEntered(info _: DropInfo) {\n        guard let dragging, dragging != self.item else { return }\n        guard let fromIndex = self.providers.firstIndex(of: dragging),\n              let toIndex = self.providers.firstIndex(of: self.item)\n        else { return }\n\n        if fromIndex == toIndex { return }\n        let adjustedIndex = toIndex > fromIndex ? toIndex + 1 : toIndex\n        self.moveProviders(IndexSet(integer: fromIndex), adjustedIndex)\n    }\n\n    func dropUpdated(info _: DropInfo) -> DropProposal? {\n        DropProposal(operation: .move)\n    }\n\n    func performDrop(info _: DropInfo) -> Bool {\n        self.dragging = nil\n        return true\n    }\n}\n\nprivate struct ProviderStatusDot: View {\n    let indicator: ProviderStatusIndicator\n\n    var body: some View {\n        Circle()\n            .fill(self.statusColor)\n            .frame(width: 6, height: 6)\n            .accessibilityHidden(true)\n    }\n\n    private var statusColor: Color {\n        switch self.indicator {\n        case .none: .green\n        case .minor: .yellow\n        case .major: .orange\n        case .critical: .red\n        case .maintenance: .gray\n        case .unknown: .gray\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/PreferencesProvidersPane+Testing.swift",
    "content": "#if DEBUG\nimport CodexBarCore\nimport SwiftUI\n\nextension ProvidersPane {\n    func _test_binding(for provider: UsageProvider) -> Binding<Bool> {\n        self.binding(for: provider)\n    }\n\n    func _test_providerSubtitle(_ provider: UsageProvider) -> String {\n        self.providerSubtitle(provider)\n    }\n\n    func _test_menuBarMetricPicker(for provider: UsageProvider) -> ProviderSettingsPickerDescriptor? {\n        self.menuBarMetricPicker(for: provider)\n    }\n\n    func _test_tokenAccountDescriptor(for provider: UsageProvider) -> ProviderSettingsTokenAccountsDescriptor? {\n        self.tokenAccountDescriptor(for: provider)\n    }\n\n    func _test_menuCardModel(for provider: UsageProvider) -> UsageMenuCardView.Model {\n        self.menuCardModel(for: provider)\n    }\n}\n\n@MainActor\nenum ProvidersPaneTestHarness {\n    static func exercise(settings: SettingsStore, store: UsageStore) {\n        self.prepareTestState(settings: settings, store: store)\n        let pane = ProvidersPane(settings: settings, store: store)\n        self.exercisePaneBasics(pane: pane)\n\n        let descriptors = self.makeDescriptors()\n        self.exerciseDetailViews(store: store, descriptors: descriptors)\n    }\n\n    private static func prepareTestState(settings: SettingsStore, store: UsageStore) {\n        store.versions[.codex] = \"1.0.0\"\n        store.versions[.claude] = \"2.0.0 (build 123)\"\n        store.versions[.cursor] = nil\n        store._setSnapshotForTesting(\n            UsageSnapshot(primary: nil, secondary: nil, updatedAt: Date()),\n            provider: .codex)\n        store._setSnapshotForTesting(\n            UsageSnapshot(primary: nil, secondary: nil, updatedAt: Date()),\n            provider: .minimax)\n        store._setErrorForTesting(String(repeating: \"x\", count: 200), provider: .cursor)\n        store.lastSourceLabels[.minimax] = \"cookies\"\n        store.refreshingProviders.insert(.codex)\n\n        settings.claudeCookieSource = .manual\n        settings.cursorCookieSource = .manual\n        settings.opencodeCookieSource = .manual\n        settings.factoryCookieSource = .manual\n        settings.minimaxCookieSource = .manual\n        settings.augmentCookieSource = .manual\n    }\n\n    private static func exercisePaneBasics(pane: ProvidersPane) {\n        _ = pane._test_binding(for: .codex).wrappedValue\n        _ = pane._test_providerSubtitle(.codex)\n        _ = pane._test_providerSubtitle(.claude)\n        _ = pane._test_providerSubtitle(.cursor)\n        _ = pane._test_providerSubtitle(.opencode)\n        _ = pane._test_providerSubtitle(.zai)\n        _ = pane._test_providerSubtitle(.synthetic)\n        _ = pane._test_providerSubtitle(.minimax)\n        _ = pane._test_providerSubtitle(.kimi)\n        _ = pane._test_providerSubtitle(.gemini)\n\n        _ = pane._test_menuBarMetricPicker(for: .codex)\n        _ = pane._test_menuBarMetricPicker(for: .gemini)\n        _ = pane._test_menuBarMetricPicker(for: .zai)\n\n        if let descriptor = pane._test_tokenAccountDescriptor(for: .claude) {\n            _ = descriptor.isVisible?()\n            _ = descriptor.accounts()\n        }\n    }\n\n    private static func exerciseDetailViews(store: UsageStore, descriptors: ProviderListTestDescriptors) {\n        var isEnabled = true\n        let enabledBinding = Binding(get: { isEnabled }, set: { isEnabled = $0 })\n        let pane = ProvidersPane(settings: store.settings, store: store)\n        let model = pane._test_menuCardModel(for: .codex)\n        var expanded = false\n        let expandedBinding = Binding(get: { expanded }, set: { expanded = $0 })\n\n        _ = ProviderDetailView(\n            provider: .codex,\n            store: store,\n            isEnabled: enabledBinding,\n            subtitle: \"Subtitle\",\n            model: model,\n            settingsPickers: [descriptors.picker],\n            settingsToggles: [descriptors.toggle],\n            settingsFields: [descriptors.fieldPlain, descriptors.fieldSecure],\n            settingsTokenAccounts: descriptors.tokenAccountsEmpty,\n            errorDisplay: ProviderErrorDisplay(preview: \"Preview\", full: \"Full\"),\n            isErrorExpanded: expandedBinding,\n            onCopyError: { _ in },\n            onRefresh: {}).body\n    }\n\n    private static func makeDescriptors() -> ProviderListTestDescriptors {\n        let toggleBinding = Binding(get: { true }, set: { _ in })\n        let actionBordered = ProviderSettingsActionDescriptor(\n            id: \"action-bordered\",\n            title: \"Bordered\",\n            style: .bordered,\n            isVisible: { true },\n            perform: { await Task.yield() })\n        let actionLink = ProviderSettingsActionDescriptor(\n            id: \"action-link\",\n            title: \"Link\",\n            style: .link,\n            isVisible: { true },\n            perform: { await Task.yield() })\n        let toggle = ProviderSettingsToggleDescriptor(\n            id: \"toggle\",\n            title: \"Toggle\",\n            subtitle: \"Toggle subtitle\",\n            binding: toggleBinding,\n            statusText: { \"Status\" },\n            actions: [actionBordered, actionLink],\n            isVisible: { true },\n            onChange: nil,\n            onAppDidBecomeActive: nil,\n            onAppearWhenEnabled: nil)\n        let picker = ProviderSettingsPickerDescriptor(\n            id: \"picker\",\n            title: \"Picker\",\n            subtitle: \"Picker subtitle\",\n            dynamicSubtitle: nil,\n            binding: Binding(get: { \"a\" }, set: { _ in }),\n            options: [\n                ProviderSettingsPickerOption(id: \"a\", title: \"Option A\"),\n                ProviderSettingsPickerOption(id: \"b\", title: \"Option B\"),\n            ],\n            isVisible: { true },\n            onChange: nil,\n            trailingText: { \"Trailing\" })\n        let fieldPlain = ProviderSettingsFieldDescriptor(\n            id: \"plain\",\n            title: \"Field\",\n            subtitle: \"Field subtitle\",\n            kind: .plain,\n            placeholder: \"Placeholder\",\n            binding: Binding(get: { \"\" }, set: { _ in }),\n            actions: [actionBordered],\n            isVisible: { true },\n            onActivate: nil)\n        let fieldSecure = ProviderSettingsFieldDescriptor(\n            id: \"secure\",\n            title: \"Secure\",\n            subtitle: \"Secure subtitle\",\n            kind: .secure,\n            placeholder: \"Secure\",\n            binding: Binding(get: { \"\" }, set: { _ in }),\n            actions: [actionLink],\n            isVisible: { true },\n            onActivate: nil)\n        let tokenAccountsEmpty = ProviderSettingsTokenAccountsDescriptor(\n            id: \"accounts-empty\",\n            title: \"Accounts\",\n            subtitle: \"Accounts subtitle\",\n            placeholder: \"Token\",\n            provider: .codex,\n            isVisible: { true },\n            accounts: { [] },\n            activeIndex: { 0 },\n            setActiveIndex: { _ in },\n            addAccount: { _, _ in },\n            removeAccount: { _ in },\n            openConfigFile: {},\n            reloadFromDisk: {})\n\n        return ProviderListTestDescriptors(\n            toggle: toggle,\n            picker: picker,\n            fieldPlain: fieldPlain,\n            fieldSecure: fieldSecure,\n            tokenAccountsEmpty: tokenAccountsEmpty)\n    }\n}\n\nprivate struct ProviderListTestDescriptors {\n    let toggle: ProviderSettingsToggleDescriptor\n    let picker: ProviderSettingsPickerDescriptor\n    let fieldPlain: ProviderSettingsFieldDescriptor\n    let fieldSecure: ProviderSettingsFieldDescriptor\n    let tokenAccountsEmpty: ProviderSettingsTokenAccountsDescriptor\n}\n#endif\n"
  },
  {
    "path": "Sources/CodexBar/PreferencesProvidersPane.swift",
    "content": "import AppKit\nimport CodexBarCore\nimport SwiftUI\n\n@MainActor\nstruct ProvidersPane: View {\n    @Bindable var settings: SettingsStore\n    @Bindable var store: UsageStore\n    @State private var expandedErrors: Set<UsageProvider> = []\n    @State private var settingsStatusTextByID: [String: String] = [:]\n    @State private var settingsLastAppActiveRunAtByID: [String: Date] = [:]\n    @State private var activeConfirmation: ProviderSettingsConfirmationState?\n    @State private var selectedProvider: UsageProvider?\n\n    private var providers: [UsageProvider] {\n        self.settings.orderedProviders()\n    }\n\n    var body: some View {\n        HStack(alignment: .top, spacing: 16) {\n            ProviderSidebarListView(\n                providers: self.providers,\n                store: self.store,\n                isEnabled: { provider in self.binding(for: provider) },\n                subtitle: { provider in self.providerSubtitle(provider) },\n                selection: self.$selectedProvider,\n                moveProviders: { fromOffsets, toOffset in\n                    self.settings.moveProvider(fromOffsets: fromOffsets, toOffset: toOffset)\n                })\n\n            if let provider = self.selectedProvider ?? self.providers.first {\n                ProviderDetailView(\n                    provider: provider,\n                    store: self.store,\n                    isEnabled: self.binding(for: provider),\n                    subtitle: self.providerSubtitle(provider),\n                    model: self.menuCardModel(for: provider),\n                    settingsPickers: self.extraSettingsPickers(for: provider),\n                    settingsToggles: self.extraSettingsToggles(for: provider),\n                    settingsFields: self.extraSettingsFields(for: provider),\n                    settingsTokenAccounts: self.tokenAccountDescriptor(for: provider),\n                    errorDisplay: self.providerErrorDisplay(provider),\n                    isErrorExpanded: self.expandedBinding(for: provider),\n                    onCopyError: { text in self.copyToPasteboard(text) },\n                    onRefresh: {\n                        Task { @MainActor in\n                            await ProviderInteractionContext.$current.withValue(.userInitiated) {\n                                await self.store.refreshProvider(provider, allowDisabled: true)\n                            }\n                        }\n                    })\n            } else {\n                Text(\"Select a provider\")\n                    .foregroundStyle(.secondary)\n                    .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)\n            }\n        }\n        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)\n        .onAppear {\n            self.ensureSelection()\n        }\n        .onChange(of: self.providers) { _, _ in\n            self.ensureSelection()\n        }\n        .onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in\n            self.runSettingsDidBecomeActiveHooks()\n        }\n        .alert(\n            self.activeConfirmation?.title ?? \"\",\n            isPresented: Binding(\n                get: { self.activeConfirmation != nil },\n                set: { isPresented in\n                    if !isPresented { self.activeConfirmation = nil }\n                }),\n            actions: {\n                if let active = self.activeConfirmation {\n                    Button(active.confirmTitle) {\n                        active.onConfirm()\n                        self.activeConfirmation = nil\n                    }\n                    Button(\"Cancel\", role: .cancel) { self.activeConfirmation = nil }\n                }\n            },\n            message: {\n                if let active = self.activeConfirmation {\n                    Text(active.message)\n                }\n            })\n    }\n\n    private func ensureSelection() {\n        guard !self.providers.isEmpty else {\n            self.selectedProvider = nil\n            return\n        }\n        if let selected = self.selectedProvider, self.providers.contains(selected) {\n            return\n        }\n        self.selectedProvider = self.providers.first\n    }\n\n    func binding(for provider: UsageProvider) -> Binding<Bool> {\n        let meta = self.store.metadata(for: provider)\n        return Binding(\n            get: { self.settings.isProviderEnabled(provider: provider, metadata: meta) },\n            set: { newValue in\n                self.settings.setProviderEnabled(provider: provider, metadata: meta, enabled: newValue)\n            })\n    }\n\n    func providerSubtitle(_ provider: UsageProvider) -> String {\n        let meta = self.store.metadata(for: provider)\n        let usageText: String\n        if let snapshot = self.store.snapshot(for: provider) {\n            let relative = snapshot.updatedAt.relativeDescription()\n            usageText = relative\n        } else if self.store.isStale(provider: provider) {\n            usageText = \"last fetch failed\"\n        } else {\n            usageText = \"usage not fetched yet\"\n        }\n\n        let presentationContext = ProviderPresentationContext(\n            provider: provider,\n            settings: self.settings,\n            store: self.store,\n            metadata: meta)\n        let presentation = ProviderCatalog.implementation(for: provider)?\n            .presentation(context: presentationContext)\n            ?? ProviderPresentation(detailLine: ProviderPresentation.standardDetailLine)\n        let detailLine = presentation.detailLine(presentationContext)\n\n        return \"\\(detailLine)\\n\\(usageText)\"\n    }\n\n    private func providerErrorDisplay(_ provider: UsageProvider) -> ProviderErrorDisplay? {\n        guard let raw = self.store.error(for: provider), !raw.isEmpty else { return nil }\n        return ProviderErrorDisplay(\n            preview: self.truncated(raw, prefix: \"\"),\n            full: raw)\n    }\n\n    private func extraSettingsToggles(for provider: UsageProvider) -> [ProviderSettingsToggleDescriptor] {\n        guard let impl = ProviderCatalog.implementation(for: provider) else { return [] }\n        let context = self.makeSettingsContext(provider: provider)\n        return impl.settingsToggles(context: context)\n            .filter { $0.isVisible?() ?? true }\n    }\n\n    private func extraSettingsPickers(for provider: UsageProvider) -> [ProviderSettingsPickerDescriptor] {\n        guard let impl = ProviderCatalog.implementation(for: provider) else { return [] }\n        let context = self.makeSettingsContext(provider: provider)\n        let providerPickers = impl.settingsPickers(context: context)\n            .filter { $0.isVisible?() ?? true }\n        if let menuBarPicker = self.menuBarMetricPicker(for: provider) {\n            return [menuBarPicker] + providerPickers\n        }\n        return providerPickers\n    }\n\n    private func extraSettingsFields(for provider: UsageProvider) -> [ProviderSettingsFieldDescriptor] {\n        guard let impl = ProviderCatalog.implementation(for: provider) else { return [] }\n        let context = self.makeSettingsContext(provider: provider)\n        return impl.settingsFields(context: context)\n            .filter { $0.isVisible?() ?? true }\n    }\n\n    func tokenAccountDescriptor(for provider: UsageProvider) -> ProviderSettingsTokenAccountsDescriptor? {\n        guard let support = TokenAccountSupportCatalog.support(for: provider) else { return nil }\n        let context = self.makeSettingsContext(provider: provider)\n        return ProviderSettingsTokenAccountsDescriptor(\n            id: \"token-accounts-\\(provider.rawValue)\",\n            title: support.title,\n            subtitle: support.subtitle,\n            placeholder: support.placeholder,\n            provider: provider,\n            isVisible: {\n                ProviderCatalog.implementation(for: provider)?\n                    .tokenAccountsVisibility(context: context, support: support)\n                    ?? (!support.requiresManualCookieSource ||\n                        !context.settings.tokenAccounts(for: provider).isEmpty)\n            },\n            accounts: { self.settings.tokenAccounts(for: provider) },\n            activeIndex: {\n                let data = self.settings.tokenAccountsData(for: provider)\n                return data?.clampedActiveIndex() ?? 0\n            },\n            setActiveIndex: { index in\n                self.settings.setActiveTokenAccountIndex(index, for: provider)\n                Task { @MainActor in\n                    await ProviderInteractionContext.$current.withValue(.userInitiated) {\n                        await self.store.refreshProvider(provider, allowDisabled: true)\n                    }\n                }\n            },\n            addAccount: { label, token in\n                self.settings.addTokenAccount(provider: provider, label: label, token: token)\n                Task { @MainActor in\n                    await ProviderInteractionContext.$current.withValue(.userInitiated) {\n                        await self.store.refreshProvider(provider, allowDisabled: true)\n                    }\n                }\n            },\n            removeAccount: { accountID in\n                self.settings.removeTokenAccount(provider: provider, accountID: accountID)\n                Task { @MainActor in\n                    await ProviderInteractionContext.$current.withValue(.userInitiated) {\n                        await self.store.refreshProvider(provider, allowDisabled: true)\n                    }\n                }\n            },\n            openConfigFile: {\n                self.settings.openTokenAccountsFile()\n            },\n            reloadFromDisk: {\n                self.settings.reloadTokenAccounts()\n                Task { @MainActor in\n                    await ProviderInteractionContext.$current.withValue(.userInitiated) {\n                        await self.store.refreshProvider(provider, allowDisabled: true)\n                    }\n                }\n            })\n    }\n\n    private func makeSettingsContext(provider: UsageProvider) -> ProviderSettingsContext {\n        ProviderSettingsContext(\n            provider: provider,\n            settings: self.settings,\n            store: self.store,\n            boolBinding: { keyPath in\n                Binding(\n                    get: { self.settings[keyPath: keyPath] },\n                    set: { self.settings[keyPath: keyPath] = $0 })\n            },\n            stringBinding: { keyPath in\n                Binding(\n                    get: { self.settings[keyPath: keyPath] },\n                    set: { self.settings[keyPath: keyPath] = $0 })\n            },\n            statusText: { id in\n                self.settingsStatusTextByID[id]\n            },\n            setStatusText: { id, text in\n                if let text {\n                    self.settingsStatusTextByID[id] = text\n                } else {\n                    self.settingsStatusTextByID.removeValue(forKey: id)\n                }\n            },\n            lastAppActiveRunAt: { id in\n                self.settingsLastAppActiveRunAtByID[id]\n            },\n            setLastAppActiveRunAt: { id, date in\n                if let date {\n                    self.settingsLastAppActiveRunAtByID[id] = date\n                } else {\n                    self.settingsLastAppActiveRunAtByID.removeValue(forKey: id)\n                }\n            },\n            requestConfirmation: { confirmation in\n                self.activeConfirmation = ProviderSettingsConfirmationState(confirmation: confirmation)\n            })\n    }\n\n    func menuBarMetricPicker(for provider: UsageProvider) -> ProviderSettingsPickerDescriptor? {\n        if provider == .zai { return nil }\n        let options: [ProviderSettingsPickerOption]\n        if provider == .openrouter {\n            options = [\n                ProviderSettingsPickerOption(id: MenuBarMetricPreference.automatic.rawValue, title: \"Automatic\"),\n                ProviderSettingsPickerOption(\n                    id: MenuBarMetricPreference.primary.rawValue,\n                    title: \"Primary (API key limit)\"),\n            ]\n        } else {\n            let metadata = self.store.metadata(for: provider)\n            let supportsAverage = self.settings.menuBarMetricSupportsAverage(for: provider)\n            var metricOptions: [ProviderSettingsPickerOption] = [\n                ProviderSettingsPickerOption(id: MenuBarMetricPreference.automatic.rawValue, title: \"Automatic\"),\n                ProviderSettingsPickerOption(\n                    id: MenuBarMetricPreference.primary.rawValue,\n                    title: \"Primary (\\(metadata.sessionLabel))\"),\n                ProviderSettingsPickerOption(\n                    id: MenuBarMetricPreference.secondary.rawValue,\n                    title: \"Secondary (\\(metadata.weeklyLabel))\"),\n            ]\n            if supportsAverage {\n                metricOptions.append(ProviderSettingsPickerOption(\n                    id: MenuBarMetricPreference.average.rawValue,\n                    title: \"Average (\\(metadata.sessionLabel) + \\(metadata.weeklyLabel))\"))\n            }\n            options = metricOptions\n        }\n        return ProviderSettingsPickerDescriptor(\n            id: \"menuBarMetric\",\n            title: \"Menu bar metric\",\n            subtitle: \"Choose which window drives the menu bar percent.\",\n            binding: Binding(\n                get: { self.settings.menuBarMetricPreference(for: provider).rawValue },\n                set: { rawValue in\n                    guard let preference = MenuBarMetricPreference(rawValue: rawValue) else { return }\n                    self.settings.setMenuBarMetricPreference(preference, for: provider)\n                }),\n            options: options,\n            isVisible: { true },\n            onChange: nil)\n    }\n\n    func menuCardModel(for provider: UsageProvider) -> UsageMenuCardView.Model {\n        let metadata = self.store.metadata(for: provider)\n        let snapshot = self.store.snapshot(for: provider)\n        let credits: CreditsSnapshot?\n        let creditsError: String?\n        let dashboard: OpenAIDashboardSnapshot?\n        let dashboardError: String?\n        let tokenSnapshot: CostUsageTokenSnapshot?\n        let tokenError: String?\n        if provider == .codex {\n            credits = self.store.credits\n            creditsError = self.store.lastCreditsError\n            dashboard = self.store.openAIDashboardRequiresLogin ? nil : self.store.openAIDashboard\n            dashboardError = self.store.lastOpenAIDashboardError\n            tokenSnapshot = self.store.tokenSnapshot(for: provider)\n            tokenError = self.store.tokenError(for: provider)\n        } else if provider == .claude || provider == .vertexai {\n            credits = nil\n            creditsError = nil\n            dashboard = nil\n            dashboardError = nil\n            tokenSnapshot = self.store.tokenSnapshot(for: provider)\n            tokenError = self.store.tokenError(for: provider)\n        } else {\n            credits = nil\n            creditsError = nil\n            dashboard = nil\n            dashboardError = nil\n            tokenSnapshot = nil\n            tokenError = nil\n        }\n\n        let now = Date()\n        let weeklyPace = snapshot?.secondary.flatMap { window in\n            self.store.weeklyPace(provider: provider, window: window, now: now)\n        }\n        let input = UsageMenuCardView.Model.Input(\n            provider: provider,\n            metadata: metadata,\n            snapshot: snapshot,\n            credits: credits,\n            creditsError: creditsError,\n            dashboard: dashboard,\n            dashboardError: dashboardError,\n            tokenSnapshot: tokenSnapshot,\n            tokenError: tokenError,\n            account: self.store.accountInfo(),\n            isRefreshing: self.store.refreshingProviders.contains(provider),\n            lastError: self.store.error(for: provider),\n            usageBarsShowUsed: self.settings.usageBarsShowUsed,\n            resetTimeDisplayStyle: self.settings.resetTimeDisplayStyle,\n            tokenCostUsageEnabled: self.settings.isCostUsageEffectivelyEnabled(for: provider),\n            showOptionalCreditsAndExtraUsage: self.settings.showOptionalCreditsAndExtraUsage,\n            hidePersonalInfo: self.settings.hidePersonalInfo,\n            weeklyPace: weeklyPace,\n            now: now)\n        return UsageMenuCardView.Model.make(input)\n    }\n\n    private func runSettingsDidBecomeActiveHooks() {\n        for provider in UsageProvider.allCases {\n            for toggle in self.extraSettingsToggles(for: provider) {\n                guard let hook = toggle.onAppDidBecomeActive else { continue }\n                Task { @MainActor in\n                    await hook()\n                }\n            }\n        }\n    }\n\n    private func truncated(_ text: String, prefix: String, maxLength: Int = 160) -> String {\n        var message = text.trimmingCharacters(in: .whitespacesAndNewlines)\n        if message.count > maxLength {\n            let idx = message.index(message.startIndex, offsetBy: maxLength)\n            message = \"\\(message[..<idx])…\"\n        }\n        return prefix + message\n    }\n\n    private func expandedBinding(for provider: UsageProvider) -> Binding<Bool> {\n        Binding(\n            get: { self.expandedErrors.contains(provider) },\n            set: { expanded in\n                if expanded {\n                    self.expandedErrors.insert(provider)\n                } else {\n                    self.expandedErrors.remove(provider)\n                }\n            })\n    }\n\n    private func copyToPasteboard(_ text: String) {\n        let pb = NSPasteboard.general\n        pb.clearContents()\n        pb.setString(text, forType: .string)\n    }\n}\n\n@MainActor\nstruct ProviderSettingsConfirmationState: Identifiable {\n    let id = UUID()\n    let title: String\n    let message: String\n    let confirmTitle: String\n    let onConfirm: () -> Void\n\n    init(confirmation: ProviderSettingsConfirmation) {\n        self.title = confirmation.title\n        self.message = confirmation.message\n        self.confirmTitle = confirmation.confirmTitle\n        self.onConfirm = confirmation.onConfirm\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/PreferencesSelection.swift",
    "content": "import Foundation\nimport Observation\n\n@MainActor\n@Observable\nfinal class PreferencesSelection {\n    var tab: PreferencesTab = .general\n}\n"
  },
  {
    "path": "Sources/CodexBar/PreferencesView.swift",
    "content": "import AppKit\nimport SwiftUI\n\nenum PreferencesTab: String, Hashable {\n    case general\n    case providers\n    case display\n    case advanced\n    case about\n    case debug\n\n    static let defaultWidth: CGFloat = 496\n    static let providersWidth: CGFloat = 720\n    static let windowHeight: CGFloat = 580\n\n    var preferredWidth: CGFloat {\n        self == .providers ? PreferencesTab.providersWidth : PreferencesTab.defaultWidth\n    }\n\n    var preferredHeight: CGFloat {\n        PreferencesTab.windowHeight\n    }\n}\n\n@MainActor\nstruct PreferencesView: View {\n    @Bindable var settings: SettingsStore\n    @Bindable var store: UsageStore\n    let updater: UpdaterProviding\n    @Bindable var selection: PreferencesSelection\n    @State private var contentWidth: CGFloat = PreferencesTab.general.preferredWidth\n    @State private var contentHeight: CGFloat = PreferencesTab.general.preferredHeight\n\n    var body: some View {\n        TabView(selection: self.$selection.tab) {\n            GeneralPane(settings: self.settings, store: self.store)\n                .tabItem { Label(\"General\", systemImage: \"gearshape\") }\n                .tag(PreferencesTab.general)\n\n            ProvidersPane(settings: self.settings, store: self.store)\n                .tabItem { Label(\"Providers\", systemImage: \"square.grid.2x2\") }\n                .tag(PreferencesTab.providers)\n\n            DisplayPane(settings: self.settings, store: self.store)\n                .tabItem { Label(\"Display\", systemImage: \"eye\") }\n                .tag(PreferencesTab.display)\n\n            AdvancedPane(settings: self.settings)\n                .tabItem { Label(\"Advanced\", systemImage: \"slider.horizontal.3\") }\n                .tag(PreferencesTab.advanced)\n\n            AboutPane(updater: self.updater)\n                .tabItem { Label(\"About\", systemImage: \"info.circle\") }\n                .tag(PreferencesTab.about)\n\n            if self.settings.debugMenuEnabled {\n                DebugPane(settings: self.settings, store: self.store)\n                    .tabItem { Label(\"Debug\", systemImage: \"ladybug\") }\n                    .tag(PreferencesTab.debug)\n            }\n        }\n        .padding(.horizontal, 24)\n        .padding(.vertical, 16)\n        .frame(width: self.contentWidth, height: self.contentHeight)\n        .onAppear {\n            self.updateLayout(for: self.selection.tab, animate: false)\n            self.ensureValidTabSelection()\n        }\n        .onChange(of: self.selection.tab) { _, newValue in\n            self.updateLayout(for: newValue, animate: true)\n        }\n        .onChange(of: self.settings.debugMenuEnabled) { _, _ in\n            self.ensureValidTabSelection()\n        }\n    }\n\n    private func updateLayout(for tab: PreferencesTab, animate: Bool) {\n        let change = {\n            self.contentWidth = tab.preferredWidth\n            self.contentHeight = tab.preferredHeight\n        }\n        if animate {\n            withAnimation(.spring(response: 0.32, dampingFraction: 0.85)) { change() }\n        } else {\n            change()\n        }\n    }\n\n    private func ensureValidTabSelection() {\n        if !self.settings.debugMenuEnabled, self.selection.tab == .debug {\n            self.selection.tab = .general\n            self.updateLayout(for: .general, animate: true)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/ProviderBrandIcon.swift",
    "content": "import AppKit\nimport CodexBarCore\n\nenum ProviderBrandIcon {\n    private static let size = NSSize(width: 16, height: 16)\n\n    /// Lazy-loaded resource bundle for provider icons.\n    private static let resourceBundle: Bundle? = {\n        // SwiftPM creates a CodexBar_CodexBar.bundle for resources in the CodexBar target.\n        if let bundleURL = Bundle.main.url(forResource: \"CodexBar_CodexBar\", withExtension: \"bundle\"),\n           let bundle = Bundle(url: bundleURL)\n        {\n            return bundle\n        }\n        // Fallback to main bundle for development/testing.\n        return Bundle.main\n    }()\n\n    static func image(for provider: UsageProvider) -> NSImage? {\n        let baseName = ProviderDescriptorRegistry.descriptor(for: provider).branding.iconResourceName\n        guard let bundle = self.resourceBundle,\n              let url = bundle.url(forResource: baseName, withExtension: \"svg\"),\n              let image = NSImage(contentsOf: url)\n        else {\n            return nil\n        }\n\n        image.size = self.size\n        image.isTemplate = true\n        return image\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/ProviderRegistry.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nstruct ProviderSpec {\n    let style: IconStyle\n    let isEnabled: @MainActor () -> Bool\n    let descriptor: ProviderDescriptor\n    let makeFetchContext: @MainActor () -> ProviderFetchContext\n}\n\nstruct ProviderRegistry {\n    let metadata: [UsageProvider: ProviderMetadata]\n\n    static let shared: ProviderRegistry = .init()\n\n    init(metadata: [UsageProvider: ProviderMetadata] = ProviderDescriptorRegistry.metadata) {\n        self.metadata = metadata\n    }\n\n    @MainActor\n    func specs(\n        settings: SettingsStore,\n        metadata: [UsageProvider: ProviderMetadata],\n        codexFetcher: UsageFetcher,\n        claudeFetcher: any ClaudeUsageFetching,\n        browserDetection: BrowserDetection) -> [UsageProvider: ProviderSpec]\n    {\n        var specs: [UsageProvider: ProviderSpec] = [:]\n        specs.reserveCapacity(UsageProvider.allCases.count)\n\n        for provider in UsageProvider.allCases {\n            let descriptor = ProviderDescriptorRegistry.descriptor(for: provider)\n            let meta = metadata[provider]!\n            let spec = ProviderSpec(\n                style: descriptor.branding.iconStyle,\n                isEnabled: { settings.isProviderEnabled(provider: provider, metadata: meta) },\n                descriptor: descriptor,\n                makeFetchContext: {\n                    let sourceMode = ProviderCatalog.implementation(for: provider)?\n                        .sourceMode(context: ProviderSourceModeContext(provider: provider, settings: settings))\n                        ?? .auto\n                    let snapshot = Self.makeSettingsSnapshot(settings: settings, tokenOverride: nil)\n                    let env = Self.makeEnvironment(\n                        base: ProcessInfo.processInfo.environment,\n                        provider: provider,\n                        settings: settings,\n                        tokenOverride: nil)\n                    let verbose = settings.isVerboseLoggingEnabled\n                    return ProviderFetchContext(\n                        runtime: .app,\n                        sourceMode: sourceMode,\n                        includeCredits: false,\n                        webTimeout: 60,\n                        webDebugDumpHTML: false,\n                        verbose: verbose,\n                        env: env,\n                        settings: snapshot,\n                        fetcher: codexFetcher,\n                        claudeFetcher: claudeFetcher,\n                        browserDetection: browserDetection)\n                })\n            specs[provider] = spec\n        }\n\n        return specs\n    }\n\n    @MainActor\n    static func makeSettingsSnapshot(\n        settings: SettingsStore,\n        tokenOverride: TokenAccountOverride?) -> ProviderSettingsSnapshot\n    {\n        settings.ensureTokenAccountsLoaded()\n        var builder = ProviderSettingsSnapshotBuilder(\n            debugMenuEnabled: settings.debugMenuEnabled,\n            debugKeepCLISessionsAlive: settings.debugKeepCLISessionsAlive)\n        let context = ProviderSettingsSnapshotContext(settings: settings, tokenOverride: tokenOverride)\n        for implementation in ProviderCatalog.all {\n            if let contribution = implementation.settingsSnapshot(context: context) {\n                builder.apply(contribution)\n            }\n        }\n        return builder.build()\n    }\n\n    @MainActor\n    static func makeEnvironment(\n        base: [String: String],\n        provider: UsageProvider,\n        settings: SettingsStore,\n        tokenOverride: TokenAccountOverride?) -> [String: String]\n    {\n        let account = ProviderTokenAccountSelection.selectedAccount(\n            provider: provider,\n            settings: settings,\n            override: tokenOverride)\n        var env = ProviderConfigEnvironment.applyAPIKeyOverride(\n            base: base,\n            provider: provider,\n            config: settings.providerConfig(for: provider))\n        // If token account is selected, use its token instead of config's apiKey\n        if let account, let override = TokenAccountSupportCatalog.envOverride(\n            for: provider,\n            token: account.token)\n        {\n            for (key, value) in override {\n                env[key] = value\n            }\n        }\n        return env\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/ProviderSwitcherButtons.swift",
    "content": "import AppKit\n\nfinal class PaddedToggleButton: NSButton {\n    var contentPadding = NSEdgeInsets(top: 4, left: 7, bottom: 4, right: 7) {\n        didSet {\n            if oldValue.top != self.contentPadding.top ||\n                oldValue.left != self.contentPadding.left ||\n                oldValue.bottom != self.contentPadding.bottom ||\n                oldValue.right != self.contentPadding.right\n            {\n                self.invalidateIntrinsicContentSize()\n            }\n        }\n    }\n\n    override var intrinsicContentSize: NSSize {\n        let size = super.intrinsicContentSize\n        return NSSize(\n            width: size.width + self.contentPadding.left + self.contentPadding.right,\n            height: size.height + self.contentPadding.top + self.contentPadding.bottom)\n    }\n}\n\nfinal class InlineIconToggleButton: NSButton {\n    private let iconView = NSImageView()\n    private let titleField = NSTextField(labelWithString: \"\")\n    private let stack = NSStackView()\n    private var paddingConstraints: [NSLayoutConstraint] = []\n    private var iconSizeConstraints: [NSLayoutConstraint] = []\n    private var isConfiguring = false // Batch invalidation during setup\n\n    var contentPadding = NSEdgeInsets(top: 4, left: 7, bottom: 4, right: 7) {\n        didSet {\n            self.paddingConstraints.first { $0.firstAttribute == .top }?.constant = self.contentPadding.top\n            self.paddingConstraints.first { $0.firstAttribute == .leading }?.constant = self.contentPadding.left\n            self.paddingConstraints.first { $0.firstAttribute == .trailing }?.constant = -self.contentPadding.right\n            self.paddingConstraints.first { $0.firstAttribute == .bottom }?.constant = -(self.contentPadding.bottom + 4)\n            if !self.isConfiguring { self.invalidateIntrinsicContentSize() }\n        }\n    }\n\n    override var title: String {\n        get { \"\" }\n        set {\n            super.title = \"\"\n            super.alternateTitle = \"\"\n            super.attributedTitle = NSAttributedString(string: \"\")\n            super.attributedAlternateTitle = NSAttributedString(string: \"\")\n            self.titleField.stringValue = newValue\n            if !self.isConfiguring { self.invalidateIntrinsicContentSize() }\n        }\n    }\n\n    override var image: NSImage? {\n        get { nil }\n        set {\n            super.image = nil\n            super.alternateImage = nil\n            self.iconView.image = newValue\n            if !self.isConfiguring { self.invalidateIntrinsicContentSize() }\n        }\n    }\n\n    func setContentTintColor(_ color: NSColor?) {\n        self.iconView.contentTintColor = color\n        self.titleField.textColor = color\n    }\n\n    func setTitleFontSize(_ size: CGFloat) {\n        self.titleField.font = NSFont.systemFont(ofSize: size)\n    }\n\n    func setAllowsTwoLineTitle(_ allow: Bool) {\n        let hasWhitespace = self.titleField.stringValue.rangeOfCharacter(from: .whitespacesAndNewlines) != nil\n        let shouldWrap = allow && hasWhitespace\n        self.titleField.maximumNumberOfLines = shouldWrap ? 2 : 1\n        self.titleField.usesSingleLineMode = !shouldWrap\n        self.titleField.lineBreakMode = shouldWrap ? .byWordWrapping : .byTruncatingTail\n    }\n\n    override var intrinsicContentSize: NSSize {\n        let size = self.stack.fittingSize\n        return NSSize(\n            width: size.width + self.contentPadding.left + self.contentPadding.right,\n            height: size.height + self.contentPadding.top + self.contentPadding.bottom)\n    }\n\n    init(title: String, image: NSImage, target: AnyObject?, action: Selector?) {\n        super.init(frame: .zero)\n        self.target = target\n        self.action = action\n        self.isConfiguring = true // Batch invalidations during setup\n        self.configure()\n        self.title = title\n        self.image = image\n        self.isConfiguring = false\n        self.invalidateIntrinsicContentSize() // Single invalidation after setup\n    }\n\n    @available(*, unavailable)\n    required init?(coder: NSCoder) {\n        nil\n    }\n\n    private func configure() {\n        self.bezelStyle = .regularSquare\n        self.isBordered = false\n        self.setButtonType(.toggle)\n        self.controlSize = .small\n        self.wantsLayer = true\n\n        self.iconView.imageScaling = .scaleNone\n        self.iconView.translatesAutoresizingMaskIntoConstraints = false\n        self.titleField.font = NSFont.systemFont(ofSize: NSFont.smallSystemFontSize)\n        self.titleField.alignment = .left\n        self.titleField.lineBreakMode = .byTruncatingTail\n        self.titleField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)\n        self.titleField.setContentHuggingPriority(.defaultLow, for: .horizontal)\n        self.setContentTintColor(NSColor.secondaryLabelColor)\n\n        self.stack.orientation = .horizontal\n        self.stack.alignment = .centerY\n        self.stack.spacing = 1\n        self.stack.translatesAutoresizingMaskIntoConstraints = false\n        self.stack.addArrangedSubview(self.iconView)\n        self.stack.addArrangedSubview(self.titleField)\n        self.addSubview(self.stack)\n\n        let iconWidth = self.iconView.widthAnchor.constraint(equalToConstant: 16)\n        let iconHeight = self.iconView.heightAnchor.constraint(equalToConstant: 16)\n        self.iconSizeConstraints = [iconWidth, iconHeight]\n\n        let top = self.stack.topAnchor.constraint(\n            equalTo: self.topAnchor,\n            constant: self.contentPadding.top)\n        let leading = self.stack.leadingAnchor.constraint(\n            greaterThanOrEqualTo: self.leadingAnchor,\n            constant: self.contentPadding.left)\n        let trailing = self.stack.trailingAnchor.constraint(\n            lessThanOrEqualTo: self.trailingAnchor,\n            constant: -self.contentPadding.right)\n        let centerX = self.stack.centerXAnchor.constraint(equalTo: self.centerXAnchor)\n        centerX.priority = .defaultHigh\n        let bottom = self.stack.bottomAnchor.constraint(\n            lessThanOrEqualTo: self.bottomAnchor,\n            constant: -(self.contentPadding.bottom + 4))\n        self.paddingConstraints = [top, leading, trailing, bottom, centerX]\n\n        NSLayoutConstraint.activate(self.paddingConstraints + self.iconSizeConstraints)\n    }\n}\n\nfinal class StackedToggleButton: NSButton {\n    private let iconView = NSImageView()\n    private let titleField = NSTextField(labelWithString: \"\")\n    private let stack = NSStackView()\n    private var paddingConstraints: [NSLayoutConstraint] = []\n    private var iconSizeConstraints: [NSLayoutConstraint] = []\n    private var isConfiguring = false // Batch invalidation during setup\n\n    var contentPadding = NSEdgeInsets(top: 2, left: 4, bottom: 2, right: 4) {\n        didSet {\n            self.paddingConstraints.first { $0.firstAttribute == .top }?.constant = self.contentPadding.top\n            self.paddingConstraints.first { $0.firstAttribute == .leading }?.constant = self.contentPadding.left\n            self.paddingConstraints.first { $0.firstAttribute == .trailing }?.constant = -self.contentPadding.right\n            self.paddingConstraints.first { $0.firstAttribute == .bottom }?.constant = -self.contentPadding.bottom\n            if !self.isConfiguring { self.invalidateIntrinsicContentSize() }\n        }\n    }\n\n    override var title: String {\n        get { \"\" }\n        set {\n            super.title = \"\"\n            super.alternateTitle = \"\"\n            super.attributedTitle = NSAttributedString(string: \"\")\n            super.attributedAlternateTitle = NSAttributedString(string: \"\")\n            self.titleField.stringValue = newValue\n            if !self.isConfiguring { self.invalidateIntrinsicContentSize() }\n        }\n    }\n\n    override var image: NSImage? {\n        get { nil }\n        set {\n            super.image = nil\n            super.alternateImage = nil\n            self.iconView.image = newValue\n            if !self.isConfiguring { self.invalidateIntrinsicContentSize() }\n        }\n    }\n\n    func setContentTintColor(_ color: NSColor?) {\n        self.iconView.contentTintColor = color\n        self.titleField.textColor = color\n    }\n\n    func setTitleFontSize(_ size: CGFloat) {\n        self.titleField.font = NSFont.systemFont(ofSize: size)\n    }\n\n    func setAllowsTwoLineTitle(_ allow: Bool) {\n        let hasWhitespace = self.titleField.stringValue.rangeOfCharacter(from: .whitespacesAndNewlines) != nil\n        let shouldWrap = allow && hasWhitespace\n        self.titleField.maximumNumberOfLines = shouldWrap ? 2 : 1\n        self.titleField.usesSingleLineMode = !shouldWrap\n        self.titleField.lineBreakMode = shouldWrap ? .byWordWrapping : .byTruncatingTail\n    }\n\n    override var intrinsicContentSize: NSSize {\n        let size = self.stack.fittingSize\n        return NSSize(\n            width: size.width + self.contentPadding.left + self.contentPadding.right,\n            height: size.height + self.contentPadding.top + self.contentPadding.bottom)\n    }\n\n    init(title: String, image: NSImage, target: AnyObject?, action: Selector?) {\n        super.init(frame: .zero)\n        self.target = target\n        self.action = action\n        self.isConfiguring = true // Batch invalidations during setup\n        self.configure()\n        self.title = title\n        self.image = image\n        self.isConfiguring = false\n        self.invalidateIntrinsicContentSize() // Single invalidation after setup\n    }\n\n    @available(*, unavailable)\n    required init?(coder: NSCoder) {\n        nil\n    }\n\n    private func configure() {\n        self.bezelStyle = .regularSquare\n        self.isBordered = false\n        self.setButtonType(.toggle)\n        self.controlSize = .small\n        self.wantsLayer = true\n\n        self.iconView.imageScaling = .scaleNone\n        self.iconView.translatesAutoresizingMaskIntoConstraints = false\n        self.titleField.font = NSFont.systemFont(ofSize: NSFont.smallSystemFontSize - 2)\n        self.titleField.alignment = .center\n        self.titleField.lineBreakMode = .byTruncatingTail\n        self.titleField.maximumNumberOfLines = 1\n        self.titleField.usesSingleLineMode = true\n        self.setContentTintColor(NSColor.secondaryLabelColor)\n\n        self.stack.orientation = .vertical\n        self.stack.alignment = .centerX\n        self.stack.spacing = 0\n        self.stack.translatesAutoresizingMaskIntoConstraints = false\n        self.stack.addArrangedSubview(self.iconView)\n        self.stack.addArrangedSubview(self.titleField)\n        self.addSubview(self.stack)\n\n        let iconWidth = self.iconView.widthAnchor.constraint(equalToConstant: 16)\n        let iconHeight = self.iconView.heightAnchor.constraint(equalToConstant: 16)\n        self.iconSizeConstraints = [iconWidth, iconHeight]\n\n        // Avoid subpixel centering: pin from the top so the icon sits on whole-point coordinates.\n        // Force an even layout width (button width minus padding) so the icon doesn't land on 0.5pt centers.\n        // Reserve some bottom space for the \"weekly remaining\" indicator line.\n        let top = self.stack.topAnchor.constraint(\n            equalTo: self.topAnchor,\n            constant: self.contentPadding.top)\n        let leading = self.stack.leadingAnchor.constraint(\n            equalTo: self.leadingAnchor,\n            constant: self.contentPadding.left)\n        let trailing = self.stack.trailingAnchor.constraint(\n            equalTo: self.trailingAnchor,\n            constant: -self.contentPadding.right)\n        let bottom = self.stack.bottomAnchor.constraint(\n            lessThanOrEqualTo: self.bottomAnchor,\n            constant: -(self.contentPadding.bottom + 4))\n        self.paddingConstraints = [top, leading, trailing, bottom]\n\n        NSLayoutConstraint.activate(self.paddingConstraints + self.iconSizeConstraints)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/ProviderToggleStore.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nstruct ProviderToggleStore {\n    private let userDefaults: UserDefaults\n    private let key = \"providerToggles\"\n\n    init(userDefaults: UserDefaults = .standard) {\n        self.userDefaults = userDefaults\n    }\n\n    func isEnabled(metadata: ProviderMetadata) -> Bool {\n        self.load()[metadata.cliName] ?? metadata.defaultEnabled\n    }\n\n    func setEnabled(_ enabled: Bool, metadata: ProviderMetadata) {\n        var toggles = self.load()\n        toggles[metadata.cliName] = enabled\n        self.userDefaults.set(toggles, forKey: self.key)\n    }\n\n    private func load() -> [String: Bool] {\n        (self.userDefaults.dictionary(forKey: self.key) as? [String: Bool]) ?? [:]\n    }\n\n    func purgeLegacyKeys() {\n        self.userDefaults.removeObject(forKey: \"showCodexUsage\")\n        self.userDefaults.removeObject(forKey: \"showClaudeUsage\")\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Alibaba/AlibabaCodingPlanProviderImplementation.swift",
    "content": "import AppKit\nimport CodexBarCore\nimport CodexBarMacroSupport\nimport Foundation\nimport SwiftUI\n\n@ProviderImplementationRegistration\nstruct AlibabaCodingPlanProviderImplementation: ProviderImplementation {\n    let id: UsageProvider = .alibaba\n\n    @MainActor\n    func presentation(context _: ProviderPresentationContext) -> ProviderPresentation {\n        ProviderPresentation { context in\n            context.store.sourceLabel(for: context.provider)\n        }\n    }\n\n    @MainActor\n    func observeSettings(_ settings: SettingsStore) {\n        _ = settings.alibabaCodingPlanAPIToken\n        _ = settings.alibabaCodingPlanCookieSource\n        _ = settings.alibabaCodingPlanCookieHeader\n        _ = settings.alibabaCodingPlanAPIRegion\n    }\n\n    @MainActor\n    func settingsSnapshot(context: ProviderSettingsSnapshotContext) -> ProviderSettingsSnapshotContribution? {\n        _ = context\n        return .alibaba(context.settings.alibabaCodingPlanSettingsSnapshot())\n    }\n\n    @MainActor\n    func settingsPickers(context: ProviderSettingsContext) -> [ProviderSettingsPickerDescriptor] {\n        let binding = Binding(\n            get: { context.settings.alibabaCodingPlanAPIRegion.rawValue },\n            set: { raw in\n                context.settings\n                    .alibabaCodingPlanAPIRegion = AlibabaCodingPlanAPIRegion(rawValue: raw) ?? .international\n            })\n        let options = AlibabaCodingPlanAPIRegion.allCases.map {\n            ProviderSettingsPickerOption(id: $0.rawValue, title: $0.displayName)\n        }\n\n        let cookieBinding = Binding(\n            get: { context.settings.alibabaCodingPlanCookieSource.rawValue },\n            set: { raw in\n                context.settings.alibabaCodingPlanCookieSource = ProviderCookieSource(rawValue: raw) ?? .auto\n            })\n        let cookieOptions = ProviderCookieSourceUI.options(\n            allowsOff: false,\n            keychainDisabled: context.settings.debugDisableKeychainAccess)\n        let cookieSubtitle: () -> String? = {\n            ProviderCookieSourceUI.subtitle(\n                source: context.settings.alibabaCodingPlanCookieSource,\n                keychainDisabled: context.settings.debugDisableKeychainAccess,\n                auto: \"Automatic imports browser cookies from Model Studio/Bailian.\",\n                manual: \"Paste a Cookie header from modelstudio.console.alibabacloud.com.\",\n                off: \"Alibaba cookies are disabled.\")\n        }\n\n        return [\n            ProviderSettingsPickerDescriptor(\n                id: \"alibaba-coding-plan-cookie-source\",\n                title: \"Cookie source\",\n                subtitle: \"Automatic imports browser cookies from Model Studio/Bailian.\",\n                dynamicSubtitle: cookieSubtitle,\n                binding: cookieBinding,\n                options: cookieOptions,\n                isVisible: nil,\n                onChange: nil,\n                trailingText: {\n                    guard let entry = CookieHeaderCache.load(provider: .alibaba) else { return nil }\n                    let when = entry.storedAt.relativeDescription()\n                    return \"Cached: \\(entry.sourceLabel) • \\(when)\"\n                }),\n            ProviderSettingsPickerDescriptor(\n                id: \"alibaba-coding-plan-region\",\n                title: \"Gateway region\",\n                subtitle: \"Use international or China mainland console gateways for quota fetches.\",\n                binding: binding,\n                options: options,\n                isVisible: nil,\n                onChange: nil),\n        ]\n    }\n\n    @MainActor\n    func settingsFields(context: ProviderSettingsContext) -> [ProviderSettingsFieldDescriptor] {\n        [\n            ProviderSettingsFieldDescriptor(\n                id: \"alibaba-coding-plan-api-key\",\n                title: \"API key\",\n                subtitle: \"Stored in ~/.codexbar/config.json. Paste your Coding Plan API key from Model Studio.\",\n                kind: .secure,\n                placeholder: \"cpk-...\",\n                binding: context.stringBinding(\\.alibabaCodingPlanAPIToken),\n                actions: [\n                    ProviderSettingsActionDescriptor(\n                        id: \"alibaba-coding-plan-open-dashboard\",\n                        title: \"Open Coding Plan\",\n                        style: .link,\n                        isVisible: nil,\n                        perform: {\n                            NSWorkspace.shared.open(context.settings.alibabaCodingPlanAPIRegion.dashboardURL)\n                        }),\n                ],\n                isVisible: nil,\n                onActivate: { context.settings.ensureAlibabaCodingPlanAPITokenLoaded() }),\n            ProviderSettingsFieldDescriptor(\n                id: \"alibaba-coding-plan-cookie\",\n                title: \"Cookie header\",\n                subtitle: \"\",\n                kind: .secure,\n                placeholder: \"Cookie: ...\",\n                binding: context.stringBinding(\\.alibabaCodingPlanCookieHeader),\n                actions: [\n                    ProviderSettingsActionDescriptor(\n                        id: \"alibaba-coding-plan-open-dashboard-cookie\",\n                        title: \"Open Coding Plan\",\n                        style: .link,\n                        isVisible: nil,\n                        perform: {\n                            NSWorkspace.shared.open(context.settings.alibabaCodingPlanAPIRegion.dashboardURL)\n                        }),\n                ],\n                isVisible: {\n                    context.settings.alibabaCodingPlanCookieSource == .manual\n                },\n                onActivate: nil),\n        ]\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Alibaba/AlibabaCodingPlanSettingsStore.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nextension SettingsStore {\n    private static let alibabaAutoEnableAppliedKey = \"alibabaCodingPlanAutoEnableApplied\"\n\n    var alibabaCodingPlanAPIRegion: AlibabaCodingPlanAPIRegion {\n        get {\n            let raw = self.configSnapshot.providerConfig(for: .alibaba)?.region\n            return AlibabaCodingPlanAPIRegion(rawValue: raw ?? \"\") ?? .international\n        }\n        set {\n            self.updateProviderConfig(provider: .alibaba) { entry in\n                entry.region = newValue.rawValue\n            }\n        }\n    }\n\n    var alibabaCodingPlanCookieHeader: String {\n        get { self.configSnapshot.providerConfig(for: .alibaba)?.sanitizedCookieHeader ?? \"\" }\n        set {\n            self.updateProviderConfig(provider: .alibaba) { entry in\n                entry.cookieHeader = self.normalizedConfigValue(newValue)\n            }\n            self.logSecretUpdate(provider: .alibaba, field: \"cookieHeader\", value: newValue)\n        }\n    }\n\n    var alibabaCodingPlanCookieSource: ProviderCookieSource {\n        get { self.resolvedCookieSource(provider: .alibaba, fallback: .auto) }\n        set {\n            self.updateProviderConfig(provider: .alibaba) { entry in\n                entry.cookieSource = newValue\n            }\n            self.logProviderModeChange(provider: .alibaba, field: \"cookieSource\", value: newValue.rawValue)\n        }\n    }\n\n    var alibabaCodingPlanAPIToken: String {\n        get { self.configSnapshot.providerConfig(for: .alibaba)?.sanitizedAPIKey ?? \"\" }\n        set {\n            self.updateProviderConfig(provider: .alibaba) { entry in\n                entry.apiKey = self.normalizedConfigValue(newValue)\n            }\n            let hasToken = !newValue.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty\n            if hasToken,\n               let metadata = ProviderDescriptorRegistry.metadata[.alibaba],\n               !self.isProviderEnabled(provider: .alibaba, metadata: metadata)\n            {\n                self.setProviderEnabled(provider: .alibaba, metadata: metadata, enabled: true)\n            }\n            self.logSecretUpdate(provider: .alibaba, field: \"apiKey\", value: newValue)\n        }\n    }\n\n    func ensureAlibabaCodingPlanAPITokenLoaded() {}\n\n    func ensureAlibabaProviderAutoEnabledIfNeeded(\n        environment: [String: String] = ProcessInfo.processInfo.environment)\n    {\n        guard self.userDefaults.bool(forKey: Self.alibabaAutoEnableAppliedKey) == false else { return }\n\n        let hasConfigToken = self.configSnapshot.providerConfig(for: .alibaba)?.sanitizedAPIKey != nil\n        let hasEnvironmentToken = AlibabaCodingPlanSettingsReader.apiToken(environment: environment) != nil\n        guard hasConfigToken || hasEnvironmentToken else { return }\n\n        if let metadata = ProviderDescriptorRegistry.metadata[.alibaba],\n           !self.isProviderEnabled(provider: .alibaba, metadata: metadata)\n        {\n            self.setProviderEnabled(provider: .alibaba, metadata: metadata, enabled: true)\n        }\n\n        self.userDefaults.set(true, forKey: Self.alibabaAutoEnableAppliedKey)\n    }\n}\n\nextension SettingsStore {\n    func alibabaCodingPlanSettingsSnapshot() -> ProviderSettingsSnapshot.AlibabaCodingPlanProviderSettings {\n        ProviderSettingsSnapshot.AlibabaCodingPlanProviderSettings(\n            cookieSource: self.alibabaCodingPlanCookieSource,\n            manualCookieHeader: self.alibabaCodingPlanCookieHeader,\n            apiRegion: self.alibabaCodingPlanAPIRegion)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Amp/AmpProviderImplementation.swift",
    "content": "import AppKit\nimport CodexBarCore\nimport CodexBarMacroSupport\nimport Foundation\nimport SwiftUI\n\n@ProviderImplementationRegistration\nstruct AmpProviderImplementation: ProviderImplementation {\n    let id: UsageProvider = .amp\n\n    @MainActor\n    func observeSettings(_ settings: SettingsStore) {\n        _ = settings.ampCookieSource\n        _ = settings.ampCookieHeader\n    }\n\n    @MainActor\n    func settingsSnapshot(context: ProviderSettingsSnapshotContext) -> ProviderSettingsSnapshotContribution? {\n        .amp(context.settings.ampSettingsSnapshot(tokenOverride: context.tokenOverride))\n    }\n\n    @MainActor\n    func settingsPickers(context: ProviderSettingsContext) -> [ProviderSettingsPickerDescriptor] {\n        let cookieBinding = Binding(\n            get: { context.settings.ampCookieSource.rawValue },\n            set: { raw in\n                context.settings.ampCookieSource = ProviderCookieSource(rawValue: raw) ?? .auto\n            })\n        let cookieOptions = ProviderCookieSourceUI.options(\n            allowsOff: false,\n            keychainDisabled: context.settings.debugDisableKeychainAccess)\n\n        let cookieSubtitle: () -> String? = {\n            ProviderCookieSourceUI.subtitle(\n                source: context.settings.ampCookieSource,\n                keychainDisabled: context.settings.debugDisableKeychainAccess,\n                auto: \"Automatic imports browser cookies.\",\n                manual: \"Paste a Cookie header or cURL capture from Amp settings.\",\n                off: \"Amp cookies are disabled.\")\n        }\n\n        return [\n            ProviderSettingsPickerDescriptor(\n                id: \"amp-cookie-source\",\n                title: \"Cookie source\",\n                subtitle: \"Automatic imports browser cookies.\",\n                dynamicSubtitle: cookieSubtitle,\n                binding: cookieBinding,\n                options: cookieOptions,\n                isVisible: nil,\n                onChange: nil),\n        ]\n    }\n\n    @MainActor\n    func settingsFields(context: ProviderSettingsContext) -> [ProviderSettingsFieldDescriptor] {\n        [\n            ProviderSettingsFieldDescriptor(\n                id: \"amp-cookie\",\n                title: \"\",\n                subtitle: \"\",\n                kind: .secure,\n                placeholder: \"Cookie: …\",\n                binding: context.stringBinding(\\.ampCookieHeader),\n                actions: [\n                    ProviderSettingsActionDescriptor(\n                        id: \"amp-open-settings\",\n                        title: \"Open Amp Settings\",\n                        style: .link,\n                        isVisible: nil,\n                        perform: {\n                            if let url = URL(string: \"https://ampcode.com/settings\") {\n                                NSWorkspace.shared.open(url)\n                            }\n                        }),\n                ],\n                isVisible: { context.settings.ampCookieSource == .manual },\n                onActivate: { context.settings.ensureAmpCookieLoaded() }),\n        ]\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Amp/AmpSettingsStore.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nextension SettingsStore {\n    var ampCookieHeader: String {\n        get { self.configSnapshot.providerConfig(for: .amp)?.sanitizedCookieHeader ?? \"\" }\n        set {\n            self.updateProviderConfig(provider: .amp) { entry in\n                entry.cookieHeader = self.normalizedConfigValue(newValue)\n            }\n            self.logSecretUpdate(provider: .amp, field: \"cookieHeader\", value: newValue)\n        }\n    }\n\n    var ampCookieSource: ProviderCookieSource {\n        get { self.resolvedCookieSource(provider: .amp, fallback: .auto) }\n        set {\n            self.updateProviderConfig(provider: .amp) { entry in\n                entry.cookieSource = newValue\n            }\n            self.logProviderModeChange(provider: .amp, field: \"cookieSource\", value: newValue.rawValue)\n        }\n    }\n\n    func ensureAmpCookieLoaded() {}\n}\n\nextension SettingsStore {\n    func ampSettingsSnapshot(tokenOverride: TokenAccountOverride?) -> ProviderSettingsSnapshot.AmpProviderSettings {\n        ProviderSettingsSnapshot.AmpProviderSettings(\n            cookieSource: self.ampSnapshotCookieSource(tokenOverride: tokenOverride),\n            manualCookieHeader: self.ampSnapshotCookieHeader(tokenOverride: tokenOverride))\n    }\n\n    private func ampSnapshotCookieHeader(tokenOverride: TokenAccountOverride?) -> String {\n        let fallback = self.ampCookieHeader\n        guard let support = TokenAccountSupportCatalog.support(for: .amp),\n              case .cookieHeader = support.injection\n        else {\n            return fallback\n        }\n        guard let account = ProviderTokenAccountSelection.selectedAccount(\n            provider: .amp,\n            settings: self,\n            override: tokenOverride)\n        else {\n            return fallback\n        }\n        return TokenAccountSupportCatalog.normalizedCookieHeader(account.token, support: support)\n    }\n\n    private func ampSnapshotCookieSource(tokenOverride: TokenAccountOverride?) -> ProviderCookieSource {\n        let fallback = self.ampCookieSource\n        guard let support = TokenAccountSupportCatalog.support(for: .amp),\n              support.requiresManualCookieSource\n        else {\n            return fallback\n        }\n        if self.tokenAccounts(for: .amp).isEmpty { return fallback }\n        return .manual\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Antigravity/AntigravityLoginFlow.swift",
    "content": "import CodexBarCore\n\n@MainActor\nextension StatusItemController {\n    func runAntigravityLoginFlow() async {\n        self.loginPhase = .idle\n        self.presentLoginAlert(\n            title: \"Antigravity login is managed in the app\",\n            message: \"Open Antigravity to sign in, then refresh CodexBar.\")\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Antigravity/AntigravityProviderImplementation.swift",
    "content": "import CodexBarCore\nimport CodexBarMacroSupport\nimport Foundation\n\n@ProviderImplementationRegistration\nstruct AntigravityProviderImplementation: ProviderImplementation {\n    let id: UsageProvider = .antigravity\n\n    func detectVersion(context _: ProviderVersionContext) async -> String? {\n        await AntigravityStatusProbe.detectVersion()\n    }\n\n    @MainActor\n    func runLoginFlow(context: ProviderLoginContext) async -> Bool {\n        await context.controller.runAntigravityLoginFlow()\n        return false\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Augment/AugmentProviderImplementation.swift",
    "content": "import AppKit\nimport CodexBarCore\nimport CodexBarMacroSupport\nimport Foundation\nimport SwiftUI\n\n@ProviderImplementationRegistration\nstruct AugmentProviderImplementation: ProviderImplementation {\n    let id: UsageProvider = .augment\n\n    @MainActor\n    func observeSettings(_ settings: SettingsStore) {\n        _ = settings.augmentCookieSource\n        _ = settings.augmentCookieHeader\n    }\n\n    @MainActor\n    func settingsSnapshot(context: ProviderSettingsSnapshotContext) -> ProviderSettingsSnapshotContribution? {\n        .augment(context.settings.augmentSettingsSnapshot(tokenOverride: context.tokenOverride))\n    }\n\n    @MainActor\n    func tokenAccountsVisibility(context: ProviderSettingsContext, support: TokenAccountSupport) -> Bool {\n        guard support.requiresManualCookieSource else { return true }\n        if !context.settings.tokenAccounts(for: context.provider).isEmpty { return true }\n        return context.settings.augmentCookieSource == .manual\n    }\n\n    @MainActor\n    func applyTokenAccountCookieSource(settings: SettingsStore) {\n        if settings.augmentCookieSource != .manual {\n            settings.augmentCookieSource = .manual\n        }\n    }\n\n    func makeRuntime() -> (any ProviderRuntime)? {\n        AugmentProviderRuntime()\n    }\n\n    @MainActor\n    func settingsPickers(context: ProviderSettingsContext) -> [ProviderSettingsPickerDescriptor] {\n        let cookieBinding = Binding(\n            get: { context.settings.augmentCookieSource.rawValue },\n            set: { raw in\n                context.settings.augmentCookieSource = ProviderCookieSource(rawValue: raw) ?? .auto\n            })\n        let cookieOptions = ProviderCookieSourceUI.options(\n            allowsOff: false,\n            keychainDisabled: context.settings.debugDisableKeychainAccess)\n\n        let cookieSubtitle: () -> String? = {\n            ProviderCookieSourceUI.subtitle(\n                source: context.settings.augmentCookieSource,\n                keychainDisabled: context.settings.debugDisableKeychainAccess,\n                auto: \"Automatic imports browser cookies.\",\n                manual: \"Paste a Cookie header or cURL capture from the Augment dashboard.\",\n                off: \"Augment cookies are disabled.\")\n        }\n\n        return [\n            ProviderSettingsPickerDescriptor(\n                id: \"augment-cookie-source\",\n                title: \"Cookie source\",\n                subtitle: \"Automatic imports browser cookies.\",\n                dynamicSubtitle: cookieSubtitle,\n                binding: cookieBinding,\n                options: cookieOptions,\n                isVisible: nil,\n                onChange: nil,\n                trailingText: {\n                    guard let entry = CookieHeaderCache.load(provider: .augment) else { return nil }\n                    let when = entry.storedAt.relativeDescription()\n                    return \"Cached: \\(entry.sourceLabel) • \\(when)\"\n                }),\n        ]\n    }\n\n    @MainActor\n    func settingsFields(context: ProviderSettingsContext) -> [ProviderSettingsFieldDescriptor] {\n        _ = context\n        return []\n    }\n\n    @MainActor\n    func appendActionMenuEntries(context: ProviderMenuActionContext, entries: inout [ProviderMenuEntry]) {\n        entries.append(.action(\"Refresh Session\", .refreshAugmentSession))\n\n        if let error = context.store.error(for: .augment) {\n            if error.contains(\"session has expired\") ||\n                error.contains(\"No Augment session cookie found\")\n            {\n                entries.append(.action(\n                    \"Open Augment (Log Out & Back In)\",\n                    .loginToProvider(url: \"https://app.augmentcode.com\")))\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Augment/AugmentProviderRuntime.swift",
    "content": "import CodexBarCore\nimport Foundation\n\n@MainActor\nfinal class AugmentProviderRuntime: ProviderRuntime {\n    let id: UsageProvider = .augment\n    private var keepalive: AugmentSessionKeepalive?\n\n    func start(context: ProviderRuntimeContext) {\n        self.updateKeepalive(context: context)\n    }\n\n    func stop(context: ProviderRuntimeContext) {\n        self.stopKeepalive(context: context, reason: \"provider disabled\")\n    }\n\n    func settingsDidChange(context: ProviderRuntimeContext) {\n        self.updateKeepalive(context: context)\n    }\n\n    func providerDidFail(context: ProviderRuntimeContext, provider: UsageProvider, error: Error) {\n        guard provider == .augment else { return }\n        let message = error.localizedDescription\n        guard message.contains(\"session expired\") else { return }\n        context.store.augmentLogger.warning(\"Augment session expired; triggering recovery\")\n        Task { [weak self] in\n            guard let self else { return }\n            await self.forceRefresh(context: context)\n        }\n    }\n\n    func perform(action: ProviderRuntimeAction, context: ProviderRuntimeContext) async {\n        switch action {\n        case .forceSessionRefresh:\n            await self.forceRefresh(context: context)\n        case .openAIWebAccessToggled:\n            break\n        }\n    }\n\n    private func updateKeepalive(context: ProviderRuntimeContext) {\n        #if os(macOS)\n        let shouldRun = context.store.isEnabled(.augment)\n        let isRunning = self.keepalive != nil\n\n        if shouldRun, !isRunning {\n            self.startKeepalive(context: context)\n        } else if !shouldRun, isRunning {\n            self.stopKeepalive(context: context, reason: \"provider disabled\")\n        }\n        #endif\n    }\n\n    private func startKeepalive(context: ProviderRuntimeContext) {\n        #if os(macOS)\n        context.store.augmentLogger.info(\n            \"Augment keepalive check\",\n            metadata: [\n                \"enabled\": context.store.isEnabled(.augment) ? \"1\" : \"0\",\n                \"available\": context.store.isProviderAvailable(.augment) ? \"1\" : \"0\",\n            ])\n\n        guard context.store.isEnabled(.augment) else {\n            context.store.augmentLogger.warning(\"Augment keepalive not started (provider disabled)\")\n            return\n        }\n\n        let logger: (String) -> Void = { [augmentLogger = context.store.augmentLogger] message in\n            augmentLogger.verbose(message)\n        }\n\n        let onSessionRecovered: () async -> Void = { [weak store = context.store] in\n            guard let store else { return }\n            store.augmentLogger.info(\"Augment session recovered; refreshing usage\")\n            await store.refreshProvider(.augment)\n        }\n\n        self.keepalive = AugmentSessionKeepalive(logger: logger, onSessionRecovered: onSessionRecovered)\n        self.keepalive?.start()\n        context.store.augmentLogger.info(\"Augment keepalive started\")\n        #endif\n    }\n\n    private func stopKeepalive(context: ProviderRuntimeContext, reason: String) {\n        #if os(macOS)\n        self.keepalive?.stop()\n        self.keepalive = nil\n        context.store.augmentLogger.info(\"Augment keepalive stopped (\\(reason))\")\n        #endif\n    }\n\n    private func forceRefresh(context: ProviderRuntimeContext) async {\n        #if os(macOS)\n        context.store.augmentLogger.info(\"Augment force refresh requested\")\n        guard let keepalive = self.keepalive else {\n            context.store.augmentLogger.warning(\"Augment keepalive not running; starting\")\n            self.startKeepalive(context: context)\n            try? await Task.sleep(for: .seconds(1))\n            guard let keepalive = self.keepalive else {\n                context.store.augmentLogger.error(\"Augment keepalive failed to start\")\n                return\n            }\n            await keepalive.forceRefresh()\n            return\n        }\n\n        await keepalive.forceRefresh()\n        context.store.augmentLogger.info(\"Refreshing Augment usage after session refresh\")\n        await context.store.refreshProvider(.augment)\n        #endif\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Augment/AugmentSettingsStore.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nextension SettingsStore {\n    var augmentCookieHeader: String {\n        get { self.configSnapshot.providerConfig(for: .augment)?.sanitizedCookieHeader ?? \"\" }\n        set {\n            self.updateProviderConfig(provider: .augment) { entry in\n                entry.cookieHeader = self.normalizedConfigValue(newValue)\n            }\n            self.logSecretUpdate(provider: .augment, field: \"cookieHeader\", value: newValue)\n        }\n    }\n\n    var augmentCookieSource: ProviderCookieSource {\n        get { self.resolvedCookieSource(provider: .augment, fallback: .auto) }\n        set {\n            self.updateProviderConfig(provider: .augment) { entry in\n                entry.cookieSource = newValue\n            }\n            self.logProviderModeChange(provider: .augment, field: \"cookieSource\", value: newValue.rawValue)\n        }\n    }\n\n    func ensureAugmentCookieLoaded() {}\n}\n\nextension SettingsStore {\n    func augmentSettingsSnapshot(tokenOverride: TokenAccountOverride?) -> ProviderSettingsSnapshot\n    .AugmentProviderSettings {\n        ProviderSettingsSnapshot.AugmentProviderSettings(\n            cookieSource: self.augmentSnapshotCookieSource(tokenOverride: tokenOverride),\n            manualCookieHeader: self.augmentSnapshotCookieHeader(tokenOverride: tokenOverride))\n    }\n\n    private func augmentSnapshotCookieHeader(tokenOverride: TokenAccountOverride?) -> String {\n        let fallback = self.augmentCookieHeader\n        guard let support = TokenAccountSupportCatalog.support(for: .augment),\n              case .cookieHeader = support.injection\n        else {\n            return fallback\n        }\n        guard let account = ProviderTokenAccountSelection.selectedAccount(\n            provider: .augment,\n            settings: self,\n            override: tokenOverride)\n        else {\n            return fallback\n        }\n        return TokenAccountSupportCatalog.normalizedCookieHeader(account.token, support: support)\n    }\n\n    private func augmentSnapshotCookieSource(tokenOverride: TokenAccountOverride?) -> ProviderCookieSource {\n        let fallback = self.augmentCookieSource\n        guard let support = TokenAccountSupportCatalog.support(for: .augment),\n              support.requiresManualCookieSource\n        else {\n            return fallback\n        }\n        if self.tokenAccounts(for: .augment).isEmpty { return fallback }\n        return .manual\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Claude/ClaudeLoginFlow.swift",
    "content": "import CodexBarCore\n\n@MainActor\nextension StatusItemController {\n    func runClaudeLoginFlow() async {\n        let phaseHandler: @Sendable (ClaudeLoginRunner.Phase) -> Void = { [weak self] phase in\n            Task { @MainActor in\n                switch phase {\n                case .requesting: self?.loginPhase = .requesting\n                case .waitingBrowser: self?.loginPhase = .waitingBrowser\n                }\n            }\n        }\n        let result = await ClaudeLoginRunner.run(timeout: 120, onPhaseChange: phaseHandler)\n        guard !Task.isCancelled else { return }\n        self.loginPhase = .idle\n        self.presentClaudeLoginResult(result)\n        let outcome = self.describe(result.outcome)\n        let length = result.output.count\n        self.loginLogger.info(\"Claude login\", metadata: [\"outcome\": outcome, \"length\": \"\\(length)\"])\n        if case .success = result.outcome {\n            self.postLoginNotification(for: .claude)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Claude/ClaudeProviderImplementation.swift",
    "content": "import CodexBarCore\nimport CodexBarMacroSupport\nimport SwiftUI\n\n@ProviderImplementationRegistration\nstruct ClaudeProviderImplementation: ProviderImplementation {\n    let id: UsageProvider = .claude\n    let supportsLoginFlow: Bool = true\n\n    @MainActor\n    func presentation(context _: ProviderPresentationContext) -> ProviderPresentation {\n        ProviderPresentation { context in\n            var versionText = context.store.version(for: context.provider) ?? \"not detected\"\n            if let parenRange = versionText.range(of: \"(\") {\n                versionText = versionText[..<parenRange.lowerBound].trimmingCharacters(in: .whitespaces)\n            }\n            return \"\\(context.metadata.cliName) \\(versionText)\"\n        }\n    }\n\n    @MainActor\n    func observeSettings(_ settings: SettingsStore) {\n        _ = settings.claudeUsageDataSource\n        _ = settings.claudeCookieSource\n        _ = settings.claudeCookieHeader\n        _ = settings.claudeOAuthKeychainPromptMode\n        _ = settings.claudeOAuthKeychainReadStrategy\n        _ = settings.claudeWebExtrasEnabled\n    }\n\n    @MainActor\n    func settingsSnapshot(context: ProviderSettingsSnapshotContext) -> ProviderSettingsSnapshotContribution? {\n        .claude(context.settings.claudeSettingsSnapshot(tokenOverride: context.tokenOverride))\n    }\n\n    @MainActor\n    func tokenAccountsVisibility(context: ProviderSettingsContext, support: TokenAccountSupport) -> Bool {\n        guard support.requiresManualCookieSource else { return true }\n        if !context.settings.tokenAccounts(for: context.provider).isEmpty { return true }\n        return context.settings.claudeCookieSource == .manual\n    }\n\n    @MainActor\n    func applyTokenAccountCookieSource(settings: SettingsStore) {\n        if settings.claudeCookieSource != .manual {\n            settings.claudeCookieSource = .manual\n        }\n    }\n\n    @MainActor\n    func defaultSourceLabel(context: ProviderSourceLabelContext) -> String? {\n        context.settings.claudeUsageDataSource.rawValue\n    }\n\n    @MainActor\n    func sourceMode(context: ProviderSourceModeContext) -> ProviderSourceMode {\n        switch context.settings.claudeUsageDataSource {\n        case .auto: .auto\n        case .oauth: .oauth\n        case .web: .web\n        case .cli: .cli\n        }\n    }\n\n    @MainActor\n    func settingsToggles(context: ProviderSettingsContext) -> [ProviderSettingsToggleDescriptor] {\n        let subtitle = if context.settings.debugDisableKeychainAccess {\n            \"Inactive while \\\"Disable Keychain access\\\" is enabled in Advanced.\"\n        } else {\n            \"Use /usr/bin/security to read Claude credentials and avoid CodexBar keychain prompts.\"\n        }\n\n        let promptFreeBinding = Binding(\n            get: { context.settings.claudeOAuthPromptFreeCredentialsEnabled },\n            set: { enabled in\n                guard !context.settings.debugDisableKeychainAccess else { return }\n                context.settings.claudeOAuthPromptFreeCredentialsEnabled = enabled\n            })\n\n        return [\n            ProviderSettingsToggleDescriptor(\n                id: \"claude-oauth-prompt-free-credentials\",\n                title: \"Avoid Keychain prompts (experimental)\",\n                subtitle: subtitle,\n                binding: promptFreeBinding,\n                statusText: nil,\n                actions: [],\n                isVisible: nil,\n                onChange: nil,\n                onAppDidBecomeActive: nil,\n                onAppearWhenEnabled: nil),\n        ]\n    }\n\n    @MainActor\n    func settingsPickers(context: ProviderSettingsContext) -> [ProviderSettingsPickerDescriptor] {\n        let usageBinding = Binding(\n            get: { context.settings.claudeUsageDataSource.rawValue },\n            set: { raw in\n                context.settings.claudeUsageDataSource = ClaudeUsageDataSource(rawValue: raw) ?? .auto\n            })\n        let cookieBinding = Binding(\n            get: { context.settings.claudeCookieSource.rawValue },\n            set: { raw in\n                context.settings.claudeCookieSource = ProviderCookieSource(rawValue: raw) ?? .auto\n            })\n        let keychainPromptPolicyBinding = Binding(\n            get: { context.settings.claudeOAuthKeychainPromptMode.rawValue },\n            set: { raw in\n                context.settings.claudeOAuthKeychainPromptMode = ClaudeOAuthKeychainPromptMode(rawValue: raw)\n                    ?? .onlyOnUserAction\n            })\n\n        let usageOptions = ClaudeUsageDataSource.allCases.map {\n            ProviderSettingsPickerOption(id: $0.rawValue, title: $0.displayName)\n        }\n        let cookieOptions = ProviderCookieSourceUI.options(\n            allowsOff: false,\n            keychainDisabled: context.settings.debugDisableKeychainAccess)\n        let keychainPromptPolicyOptions: [ProviderSettingsPickerOption] = [\n            ProviderSettingsPickerOption(\n                id: ClaudeOAuthKeychainPromptMode.never.rawValue,\n                title: \"Never prompt\"),\n            ProviderSettingsPickerOption(\n                id: ClaudeOAuthKeychainPromptMode.onlyOnUserAction.rawValue,\n                title: \"Only on user action\"),\n            ProviderSettingsPickerOption(\n                id: ClaudeOAuthKeychainPromptMode.always.rawValue,\n                title: \"Always allow prompts\"),\n        ]\n        let cookieSubtitle: () -> String? = {\n            ProviderCookieSourceUI.subtitle(\n                source: context.settings.claudeCookieSource,\n                keychainDisabled: context.settings.debugDisableKeychainAccess,\n                auto: \"Automatic imports browser cookies for the web API.\",\n                manual: \"Paste a Cookie header from a claude.ai request.\",\n                off: \"Claude cookies are disabled.\")\n        }\n        let keychainPromptPolicySubtitle: () -> String? = {\n            if context.settings.debugDisableKeychainAccess {\n                return \"Global Keychain access is disabled in Advanced, so this setting is currently inactive.\"\n            }\n            return \"Controls Claude OAuth Keychain prompts when experimental reader mode is off. Choosing \" +\n                \"\\\"Never prompt\\\" can make OAuth unavailable; use Web/CLI when needed.\"\n        }\n\n        return [\n            ProviderSettingsPickerDescriptor(\n                id: \"claude-usage-source\",\n                title: \"Usage source\",\n                subtitle: \"Auto falls back to the next source if the preferred one fails.\",\n                binding: usageBinding,\n                options: usageOptions,\n                isVisible: nil,\n                onChange: nil,\n                trailingText: {\n                    guard context.settings.claudeUsageDataSource == .auto else { return nil }\n                    let label = context.store.sourceLabel(for: .claude)\n                    return label == \"auto\" ? nil : label\n                }),\n            ProviderSettingsPickerDescriptor(\n                id: \"claude-keychain-prompt-policy\",\n                title: \"Keychain prompt policy\",\n                subtitle: \"Applies only to the Security.framework OAuth keychain reader.\",\n                dynamicSubtitle: keychainPromptPolicySubtitle,\n                binding: keychainPromptPolicyBinding,\n                options: keychainPromptPolicyOptions,\n                isVisible: { context.settings.claudeOAuthKeychainReadStrategy == .securityFramework },\n                isEnabled: { !context.settings.debugDisableKeychainAccess },\n                onChange: nil),\n            ProviderSettingsPickerDescriptor(\n                id: \"claude-cookie-source\",\n                title: \"Claude cookies\",\n                subtitle: \"Automatic imports browser cookies for the web API.\",\n                dynamicSubtitle: cookieSubtitle,\n                binding: cookieBinding,\n                options: cookieOptions,\n                isVisible: nil,\n                onChange: nil,\n                trailingText: {\n                    guard let entry = CookieHeaderCache.load(provider: .claude) else { return nil }\n                    let when = entry.storedAt.relativeDescription()\n                    return \"Cached: \\(entry.sourceLabel) • \\(when)\"\n                }),\n        ]\n    }\n\n    @MainActor\n    func settingsFields(context: ProviderSettingsContext) -> [ProviderSettingsFieldDescriptor] {\n        _ = context\n        return []\n    }\n\n    @MainActor\n    func runLoginFlow(context: ProviderLoginContext) async -> Bool {\n        await context.controller.runClaudeLoginFlow()\n        return true\n    }\n\n    @MainActor\n    func appendUsageMenuEntries(context: ProviderMenuUsageContext, entries: inout [ProviderMenuEntry]) {\n        if context.snapshot?.secondary == nil {\n            entries.append(.text(\"Weekly usage unavailable for this account.\", .secondary))\n        }\n\n        if let cost = context.snapshot?.providerCost,\n           context.settings.showOptionalCreditsAndExtraUsage,\n           cost.currencyCode != \"Quota\"\n        {\n            let used = UsageFormatter.currencyString(cost.used, currencyCode: cost.currencyCode)\n            let limit = UsageFormatter.currencyString(cost.limit, currencyCode: cost.currencyCode)\n            entries.append(.text(\"Extra usage: \\(used) / \\(limit)\", .primary))\n        }\n    }\n\n    @MainActor\n    func loginMenuAction(context: ProviderMenuLoginContext)\n        -> (label: String, action: MenuDescriptor.MenuAction)?\n    {\n        guard self.shouldOpenTerminalForOAuthError(store: context.store) else { return nil }\n        return (\"Open Terminal\", .openTerminal(command: \"claude\"))\n    }\n\n    @MainActor\n    private func shouldOpenTerminalForOAuthError(store: UsageStore) -> Bool {\n        guard store.error(for: .claude) != nil else { return false }\n        let attempts = store.fetchAttempts(for: .claude)\n        if attempts.contains(where: { $0.kind == .oauth && ($0.errorDescription?.isEmpty == false) }) {\n            return true\n        }\n        if let error = store.error(for: .claude)?.lowercased(), error.contains(\"oauth\") {\n            return true\n        }\n        return false\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Claude/ClaudeSettingsStore.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nextension SettingsStore {\n    var claudeUsageDataSource: ClaudeUsageDataSource {\n        get {\n            let source = self.configSnapshot.providerConfig(for: .claude)?.source\n            return Self.claudeUsageDataSource(from: source)\n        }\n        set {\n            let source: ProviderSourceMode? = switch newValue {\n            case .auto: .auto\n            case .oauth: .oauth\n            case .web: .web\n            case .cli: .cli\n            }\n            self.updateProviderConfig(provider: .claude) { entry in\n                entry.source = source\n            }\n            self.logProviderModeChange(provider: .claude, field: \"usageSource\", value: newValue.rawValue)\n            if newValue != .cli {\n                self.claudeWebExtrasEnabled = false\n            }\n        }\n    }\n\n    var claudeCookieHeader: String {\n        get { self.configSnapshot.providerConfig(for: .claude)?.sanitizedCookieHeader ?? \"\" }\n        set {\n            self.updateProviderConfig(provider: .claude) { entry in\n                entry.cookieHeader = self.normalizedConfigValue(newValue)\n            }\n            self.logSecretUpdate(provider: .claude, field: \"cookieHeader\", value: newValue)\n        }\n    }\n\n    var claudeCookieSource: ProviderCookieSource {\n        get { self.resolvedCookieSource(provider: .claude, fallback: .auto) }\n        set {\n            self.updateProviderConfig(provider: .claude) { entry in\n                entry.cookieSource = newValue\n            }\n            self.logProviderModeChange(provider: .claude, field: \"cookieSource\", value: newValue.rawValue)\n        }\n    }\n\n    func ensureClaudeCookieLoaded() {}\n}\n\nextension SettingsStore {\n    func claudeSettingsSnapshot(tokenOverride: TokenAccountOverride?) -> ProviderSettingsSnapshot\n    .ClaudeProviderSettings {\n        let account = self.selectedClaudeTokenAccount(tokenOverride: tokenOverride)\n        let routing = self.claudeCredentialRouting(account: account)\n        return ProviderSettingsSnapshot.ClaudeProviderSettings(\n            usageDataSource: self.claudeUsageDataSource,\n            webExtrasEnabled: self.claudeWebExtrasEnabled,\n            cookieSource: self.claudeSnapshotCookieSource(tokenOverride: tokenOverride, routing: routing),\n            manualCookieHeader: self.claudeSnapshotCookieHeader(\n                routing: routing,\n                hasSelectedAccount: account != nil))\n    }\n\n    private static func claudeUsageDataSource(from source: ProviderSourceMode?) -> ClaudeUsageDataSource {\n        guard let source else { return .auto }\n        switch source {\n        case .auto, .api:\n            return .auto\n        case .web:\n            return .web\n        case .cli:\n            return .cli\n        case .oauth:\n            return .oauth\n        }\n    }\n\n    private func claudeSnapshotCookieHeader(\n        routing: ClaudeCredentialRouting,\n        hasSelectedAccount: Bool) -> String\n    {\n        switch routing {\n        case .none:\n            hasSelectedAccount ? \"\" : self.claudeCookieHeader\n        case .oauth:\n            \"\"\n        case let .webCookie(header):\n            header\n        }\n    }\n\n    private func claudeSnapshotCookieSource(\n        tokenOverride: TokenAccountOverride?,\n        routing: ClaudeCredentialRouting) -> ProviderCookieSource\n    {\n        let fallback = self.claudeCookieSource\n        guard let support = TokenAccountSupportCatalog.support(for: .claude),\n              support.requiresManualCookieSource\n        else {\n            return fallback\n        }\n        if routing.isOAuth {\n            return .off\n        }\n        if self.tokenAccounts(for: .claude).isEmpty { return fallback }\n        return .manual\n    }\n\n    private func claudeCredentialRouting(account: ProviderTokenAccount?) -> ClaudeCredentialRouting {\n        let manualCookieHeader = account == nil ? self.claudeCookieHeader : nil\n        return ClaudeCredentialRouting.resolve(\n            tokenAccountToken: account?.token,\n            manualCookieHeader: manualCookieHeader)\n    }\n\n    private func selectedClaudeTokenAccount(tokenOverride: TokenAccountOverride?) -> ProviderTokenAccount? {\n        ProviderTokenAccountSelection.selectedAccount(\n            provider: .claude,\n            settings: self,\n            override: tokenOverride)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Codex/CodexLoginFlow.swift",
    "content": "import CodexBarCore\n\n@MainActor\nextension StatusItemController {\n    func runCodexLoginFlow() async {\n        let result = await CodexLoginRunner.run(timeout: 120)\n        guard !Task.isCancelled else { return }\n        self.loginPhase = .idle\n        self.presentCodexLoginResult(result)\n        let outcome = self.describe(result.outcome)\n        let length = result.output.count\n        self.loginLogger.info(\"Codex login\", metadata: [\"outcome\": outcome, \"length\": \"\\(length)\"])\n        if case .success = result.outcome {\n            self.postLoginNotification(for: .codex)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Codex/CodexProviderImplementation.swift",
    "content": "import CodexBarCore\nimport CodexBarMacroSupport\nimport Foundation\nimport SwiftUI\n\n@ProviderImplementationRegistration\nstruct CodexProviderImplementation: ProviderImplementation {\n    let id: UsageProvider = .codex\n    let supportsLoginFlow: Bool = true\n\n    @MainActor\n    func presentation(context _: ProviderPresentationContext) -> ProviderPresentation {\n        ProviderPresentation { context in\n            context.store.version(for: context.provider) ?? \"not detected\"\n        }\n    }\n\n    @MainActor\n    func observeSettings(_ settings: SettingsStore) {\n        _ = settings.codexUsageDataSource\n        _ = settings.codexCookieSource\n        _ = settings.codexCookieHeader\n    }\n\n    @MainActor\n    func settingsSnapshot(context: ProviderSettingsSnapshotContext) -> ProviderSettingsSnapshotContribution? {\n        .codex(context.settings.codexSettingsSnapshot(tokenOverride: context.tokenOverride))\n    }\n\n    @MainActor\n    func defaultSourceLabel(context: ProviderSourceLabelContext) -> String? {\n        context.settings.codexUsageDataSource.rawValue\n    }\n\n    @MainActor\n    func decorateSourceLabel(context: ProviderSourceLabelContext, baseLabel: String) -> String {\n        if context.settings.codexCookieSource.isEnabled,\n           context.store.openAIDashboard != nil,\n           !context.store.openAIDashboardRequiresLogin,\n           !baseLabel.contains(\"openai-web\")\n        {\n            return \"\\(baseLabel) + openai-web\"\n        }\n        return baseLabel\n    }\n\n    @MainActor\n    func sourceMode(context: ProviderSourceModeContext) -> ProviderSourceMode {\n        switch context.settings.codexUsageDataSource {\n        case .auto: .auto\n        case .oauth: .oauth\n        case .cli: .cli\n        }\n    }\n\n    func makeRuntime() -> (any ProviderRuntime)? {\n        CodexProviderRuntime()\n    }\n\n    @MainActor\n    func settingsToggles(context: ProviderSettingsContext) -> [ProviderSettingsToggleDescriptor] {\n        let extrasBinding = Binding(\n            get: { context.settings.openAIWebAccessEnabled },\n            set: { enabled in\n                context.settings.openAIWebAccessEnabled = enabled\n                Task { @MainActor in\n                    await context.store.performRuntimeAction(\n                        .openAIWebAccessToggled(enabled),\n                        for: .codex)\n                }\n            })\n\n        return [\n            ProviderSettingsToggleDescriptor(\n                id: \"codex-historical-tracking\",\n                title: \"Historical tracking\",\n                subtitle: \"Stores local Codex usage history (8 weeks) to personalize Pace predictions.\",\n                binding: context.boolBinding(\\.historicalTrackingEnabled),\n                statusText: nil,\n                actions: [],\n                isVisible: nil,\n                onChange: nil,\n                onAppDidBecomeActive: nil,\n                onAppearWhenEnabled: nil),\n            ProviderSettingsToggleDescriptor(\n                id: \"codex-openai-web-extras\",\n                title: \"OpenAI web extras\",\n                subtitle: \"Show usage breakdown, credits history, and code review via chatgpt.com.\",\n                binding: extrasBinding,\n                statusText: nil,\n                actions: [],\n                isVisible: nil,\n                onChange: nil,\n                onAppDidBecomeActive: nil,\n                onAppearWhenEnabled: nil),\n        ]\n    }\n\n    @MainActor\n    func settingsPickers(context: ProviderSettingsContext) -> [ProviderSettingsPickerDescriptor] {\n        let usageBinding = Binding(\n            get: { context.settings.codexUsageDataSource.rawValue },\n            set: { raw in\n                context.settings.codexUsageDataSource = CodexUsageDataSource(rawValue: raw) ?? .auto\n            })\n        let cookieBinding = Binding(\n            get: { context.settings.codexCookieSource.rawValue },\n            set: { raw in\n                context.settings.codexCookieSource = ProviderCookieSource(rawValue: raw) ?? .auto\n            })\n\n        let usageOptions = CodexUsageDataSource.allCases.map {\n            ProviderSettingsPickerOption(id: $0.rawValue, title: $0.displayName)\n        }\n        let cookieOptions = ProviderCookieSourceUI.options(\n            allowsOff: true,\n            keychainDisabled: context.settings.debugDisableKeychainAccess)\n\n        let cookieSubtitle: () -> String? = {\n            ProviderCookieSourceUI.subtitle(\n                source: context.settings.codexCookieSource,\n                keychainDisabled: context.settings.debugDisableKeychainAccess,\n                auto: \"Automatic imports browser cookies for dashboard extras.\",\n                manual: \"Paste a Cookie header from a chatgpt.com request.\",\n                off: \"Disable OpenAI dashboard cookie usage.\")\n        }\n\n        return [\n            ProviderSettingsPickerDescriptor(\n                id: \"codex-usage-source\",\n                title: \"Usage source\",\n                subtitle: \"Auto falls back to the next source if the preferred one fails.\",\n                binding: usageBinding,\n                options: usageOptions,\n                isVisible: nil,\n                onChange: nil,\n                trailingText: {\n                    guard context.settings.codexUsageDataSource == .auto else { return nil }\n                    let label = context.store.sourceLabel(for: .codex)\n                    return label == \"auto\" ? nil : label\n                }),\n            ProviderSettingsPickerDescriptor(\n                id: \"codex-cookie-source\",\n                title: \"OpenAI cookies\",\n                subtitle: \"Automatic imports browser cookies for dashboard extras.\",\n                dynamicSubtitle: cookieSubtitle,\n                binding: cookieBinding,\n                options: cookieOptions,\n                isVisible: { context.settings.openAIWebAccessEnabled },\n                onChange: nil,\n                trailingText: {\n                    guard let entry = CookieHeaderCache.load(provider: .codex) else { return nil }\n                    let when = entry.storedAt.relativeDescription()\n                    return \"Cached: \\(entry.sourceLabel) • \\(when)\"\n                }),\n        ]\n    }\n\n    @MainActor\n    func settingsFields(context: ProviderSettingsContext) -> [ProviderSettingsFieldDescriptor] {\n        [\n            ProviderSettingsFieldDescriptor(\n                id: \"codex-cookie-header\",\n                title: \"\",\n                subtitle: \"\",\n                kind: .secure,\n                placeholder: \"Cookie: …\",\n                binding: context.stringBinding(\\.codexCookieHeader),\n                actions: [],\n                isVisible: {\n                    context.settings.codexCookieSource == .manual\n                },\n                onActivate: { context.settings.ensureCodexCookieLoaded() }),\n        ]\n    }\n\n    @MainActor\n    func appendUsageMenuEntries(context: ProviderMenuUsageContext, entries: inout [ProviderMenuEntry]) {\n        guard context.settings.showOptionalCreditsAndExtraUsage,\n              context.metadata.supportsCredits\n        else { return }\n\n        if let credits = context.store.credits {\n            entries.append(.text(\"Credits: \\(UsageFormatter.creditsString(from: credits.remaining))\", .primary))\n            if let latest = credits.events.first {\n                entries.append(.text(\"Last spend: \\(UsageFormatter.creditEventSummary(latest))\", .secondary))\n            }\n        } else {\n            let hint = context.store.lastCreditsError ?? context.metadata.creditsHint\n            entries.append(.text(hint, .secondary))\n        }\n    }\n\n    @MainActor\n    func runLoginFlow(context: ProviderLoginContext) async -> Bool {\n        await context.controller.runCodexLoginFlow()\n        return true\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Codex/CodexProviderRuntime.swift",
    "content": "import CodexBarCore\nimport Foundation\n\n@MainActor\nfinal class CodexProviderRuntime: ProviderRuntime {\n    let id: UsageProvider = .codex\n\n    func perform(action: ProviderRuntimeAction, context: ProviderRuntimeContext) async {\n        switch action {\n        case let .openAIWebAccessToggled(enabled):\n            guard enabled == false else { return }\n            context.store.resetOpenAIWebState()\n        case .forceSessionRefresh:\n            break\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Codex/CodexSettingsStore.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nextension SettingsStore {\n    var codexUsageDataSource: CodexUsageDataSource {\n        get {\n            let source = self.configSnapshot.providerConfig(for: .codex)?.source\n            return Self.codexUsageDataSource(from: source)\n        }\n        set {\n            let source: ProviderSourceMode? = switch newValue {\n            case .auto: .auto\n            case .oauth: .oauth\n            case .cli: .cli\n            }\n            self.updateProviderConfig(provider: .codex) { entry in\n                entry.source = source\n            }\n            self.logProviderModeChange(provider: .codex, field: \"usageSource\", value: newValue.rawValue)\n        }\n    }\n\n    var codexCookieHeader: String {\n        get { self.configSnapshot.providerConfig(for: .codex)?.sanitizedCookieHeader ?? \"\" }\n        set {\n            self.updateProviderConfig(provider: .codex) { entry in\n                entry.cookieHeader = self.normalizedConfigValue(newValue)\n            }\n            self.logSecretUpdate(provider: .codex, field: \"cookieHeader\", value: newValue)\n        }\n    }\n\n    var codexCookieSource: ProviderCookieSource {\n        get {\n            let resolved = self.resolvedCookieSource(provider: .codex, fallback: .auto)\n            return self.openAIWebAccessEnabled ? resolved : .off\n        }\n        set {\n            self.updateProviderConfig(provider: .codex) { entry in\n                entry.cookieSource = newValue\n            }\n            self.logProviderModeChange(provider: .codex, field: \"cookieSource\", value: newValue.rawValue)\n            self.openAIWebAccessEnabled = newValue.isEnabled\n        }\n    }\n\n    func ensureCodexCookieLoaded() {}\n}\n\nextension SettingsStore {\n    func codexSettingsSnapshot(tokenOverride: TokenAccountOverride?) -> ProviderSettingsSnapshot.CodexProviderSettings {\n        ProviderSettingsSnapshot.CodexProviderSettings(\n            usageDataSource: self.codexUsageDataSource,\n            cookieSource: self.codexSnapshotCookieSource(tokenOverride: tokenOverride),\n            manualCookieHeader: self.codexSnapshotCookieHeader(tokenOverride: tokenOverride))\n    }\n\n    private static func codexUsageDataSource(from source: ProviderSourceMode?) -> CodexUsageDataSource {\n        guard let source else { return .auto }\n        switch source {\n        case .auto, .web, .api:\n            return .auto\n        case .cli:\n            return .cli\n        case .oauth:\n            return .oauth\n        }\n    }\n\n    private func codexSnapshotCookieHeader(tokenOverride: TokenAccountOverride?) -> String {\n        let fallback = self.codexCookieHeader\n        guard let support = TokenAccountSupportCatalog.support(for: .codex),\n              case .cookieHeader = support.injection\n        else {\n            return fallback\n        }\n        guard let account = ProviderTokenAccountSelection.selectedAccount(\n            provider: .codex,\n            settings: self,\n            override: tokenOverride)\n        else {\n            return fallback\n        }\n        return TokenAccountSupportCatalog.normalizedCookieHeader(account.token, support: support)\n    }\n\n    private func codexSnapshotCookieSource(tokenOverride: TokenAccountOverride?) -> ProviderCookieSource {\n        let fallback = self.codexCookieSource\n        guard let support = TokenAccountSupportCatalog.support(for: .codex),\n              support.requiresManualCookieSource\n        else {\n            return fallback\n        }\n        if self.tokenAccounts(for: .codex).isEmpty { return fallback }\n        return .manual\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Copilot/CopilotLoginFlow.swift",
    "content": "import AppKit\nimport CodexBarCore\nimport SwiftUI\n\n@MainActor\nstruct CopilotLoginFlow {\n    static func run(settings: SettingsStore) async {\n        let flow = CopilotDeviceFlow()\n\n        do {\n            let code = try await flow.requestDeviceCode()\n\n            // Copy code to clipboard\n            let pb = NSPasteboard.general\n            pb.clearContents()\n            pb.setString(code.userCode, forType: .string)\n\n            let alert = NSAlert()\n            alert.messageText = \"GitHub Copilot Login\"\n            alert.informativeText = \"\"\"\n            A device code has been copied to your clipboard: \\(code.userCode)\n\n            Please verify it at: \\(code.verificationUri)\n            \"\"\"\n            alert.addButton(withTitle: \"Open Browser\")\n            alert.addButton(withTitle: \"Cancel\")\n\n            let response = alert.runModal()\n            if response == .alertSecondButtonReturn {\n                return // Cancelled\n            }\n\n            if let url = URL(string: code.verificationUri) {\n                NSWorkspace.shared.open(url)\n            }\n\n            // Poll in background (modal blocks, but we need to wait for token effectively)\n            // Ideally we'd show a \"Waiting...\" modal or spinner.\n            // For simplicity, we can use a non-modal window or just block a Task?\n            // `runModal` blocks the thread. We need to poll while the user is doing auth in browser.\n            // But we already returned from runModal to open the browser.\n            // We need a secondary \"Waiting for confirmation...\" alert or state.\n\n            // Let's show a \"Waiting\" alert that can be cancelled.\n            let waitingAlert = NSAlert()\n            waitingAlert.messageText = \"Waiting for Authentication...\"\n            waitingAlert.informativeText = \"\"\"\n            Please complete the login in your browser.\n            This window will close automatically when finished.\n            \"\"\"\n            waitingAlert.addButton(withTitle: \"Cancel\")\n            let parentWindow = Self.resolveWaitingParentWindow()\n            let hostWindow = parentWindow ?? Self.makeWaitingHostWindow()\n            let shouldCloseHostWindow = parentWindow == nil\n            let tokenTask = Task.detached(priority: .userInitiated) {\n                try await flow.pollForToken(deviceCode: code.deviceCode, interval: code.interval)\n            }\n\n            let waitTask = Task { @MainActor in\n                let response = await Self.presentWaitingAlert(waitingAlert, parentWindow: hostWindow)\n                if response == .alertFirstButtonReturn {\n                    tokenTask.cancel()\n                }\n                return response\n            }\n\n            let tokenResult: Result<String, Error>\n            do {\n                let token = try await tokenTask.value\n                tokenResult = .success(token)\n            } catch {\n                tokenResult = .failure(error)\n            }\n\n            Self.dismissWaitingAlert(waitingAlert, parentWindow: hostWindow, closeHost: shouldCloseHostWindow)\n            let waitResponse = await waitTask.value\n            if waitResponse == .alertFirstButtonReturn {\n                return\n            }\n\n            switch tokenResult {\n            case let .success(token):\n                settings.copilotAPIToken = token\n                settings.setProviderEnabled(\n                    provider: .copilot,\n                    metadata: ProviderRegistry.shared.metadata[.copilot]!,\n                    enabled: true)\n\n                let success = NSAlert()\n                success.messageText = \"Login Successful\"\n                success.runModal()\n            case let .failure(error):\n                guard !(error is CancellationError) else { return }\n                let err = NSAlert()\n                err.messageText = \"Login Failed\"\n                err.informativeText = error.localizedDescription\n                err.runModal()\n            }\n\n        } catch {\n            let err = NSAlert()\n            err.messageText = \"Login Failed\"\n            err.informativeText = error.localizedDescription\n            err.runModal()\n        }\n    }\n\n    @MainActor\n    private static func presentWaitingAlert(\n        _ alert: NSAlert,\n        parentWindow: NSWindow) async -> NSApplication.ModalResponse\n    {\n        await withCheckedContinuation { continuation in\n            alert.beginSheetModal(for: parentWindow) { response in\n                continuation.resume(returning: response)\n            }\n        }\n    }\n\n    @MainActor\n    private static func dismissWaitingAlert(\n        _ alert: NSAlert,\n        parentWindow: NSWindow,\n        closeHost: Bool)\n    {\n        let alertWindow = alert.window\n        if alertWindow.sheetParent != nil {\n            parentWindow.endSheet(alertWindow)\n        } else {\n            alertWindow.orderOut(nil)\n        }\n\n        guard closeHost else { return }\n        parentWindow.orderOut(nil)\n        parentWindow.close()\n    }\n\n    @MainActor\n    private static func resolveWaitingParentWindow() -> NSWindow? {\n        if let window = NSApp.keyWindow ?? NSApp.mainWindow {\n            return window\n        }\n        if let window = NSApp.windows.first(where: { $0.isVisible && !$0.ignoresMouseEvents }) {\n            return window\n        }\n        return NSApp.windows.first\n    }\n\n    @MainActor\n    private static func makeWaitingHostWindow() -> NSWindow {\n        let window = NSWindow(\n            contentRect: NSRect(x: 0, y: 0, width: 420, height: 1),\n            styleMask: [.titled, .fullSizeContentView],\n            backing: .buffered,\n            defer: false)\n        window.isReleasedWhenClosed = false\n        window.titleVisibility = .hidden\n        window.titlebarAppearsTransparent = true\n        window.standardWindowButton(.closeButton)?.isHidden = true\n        window.standardWindowButton(.miniaturizeButton)?.isHidden = true\n        window.standardWindowButton(.zoomButton)?.isHidden = true\n        window.backgroundColor = .clear\n        window.isOpaque = false\n        window.hasShadow = false\n        window.level = .floating\n        window.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]\n        window.center()\n        window.makeKeyAndOrderFront(nil)\n        return window\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Copilot/CopilotProviderImplementation.swift",
    "content": "import AppKit\nimport CodexBarCore\nimport CodexBarMacroSupport\nimport SwiftUI\n\n@ProviderImplementationRegistration\nstruct CopilotProviderImplementation: ProviderImplementation {\n    let id: UsageProvider = .copilot\n    let supportsLoginFlow: Bool = true\n\n    @MainActor\n    func presentation(context _: ProviderPresentationContext) -> ProviderPresentation {\n        ProviderPresentation { _ in \"github api\" }\n    }\n\n    @MainActor\n    func observeSettings(_ settings: SettingsStore) {\n        _ = settings.copilotAPIToken\n    }\n\n    @MainActor\n    func settingsSnapshot(context: ProviderSettingsSnapshotContext) -> ProviderSettingsSnapshotContribution? {\n        _ = context\n        return .copilot(context.settings.copilotSettingsSnapshot())\n    }\n\n    @MainActor\n    func settingsFields(context: ProviderSettingsContext) -> [ProviderSettingsFieldDescriptor] {\n        [\n            ProviderSettingsFieldDescriptor(\n                id: \"copilot-api-token\",\n                title: \"GitHub Login\",\n                subtitle: \"Requires authentication via GitHub Device Flow.\",\n                kind: .secure,\n                placeholder: \"Sign in via button below\",\n                binding: context.stringBinding(\\.copilotAPIToken),\n                actions: [\n                    ProviderSettingsActionDescriptor(\n                        id: \"copilot-login\",\n                        title: \"Sign in with GitHub\",\n                        style: .bordered,\n                        isVisible: { context.settings.copilotAPIToken.isEmpty },\n                        perform: {\n                            await CopilotLoginFlow.run(settings: context.settings)\n                        }),\n                    ProviderSettingsActionDescriptor(\n                        id: \"copilot-relogin\",\n                        title: \"Sign in again\",\n                        style: .link,\n                        isVisible: { !context.settings.copilotAPIToken.isEmpty },\n                        perform: {\n                            await CopilotLoginFlow.run(settings: context.settings)\n                        }),\n                ],\n                isVisible: nil,\n                onActivate: { context.settings.ensureCopilotAPITokenLoaded() }),\n        ]\n    }\n\n    @MainActor\n    func runLoginFlow(context: ProviderLoginContext) async -> Bool {\n        await CopilotLoginFlow.run(settings: context.controller.settings)\n        return true\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Copilot/CopilotSettingsStore.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nextension SettingsStore {\n    var copilotAPIToken: String {\n        get { self.configSnapshot.providerConfig(for: .copilot)?.sanitizedAPIKey ?? \"\" }\n        set {\n            self.updateProviderConfig(provider: .copilot) { entry in\n                entry.apiKey = self.normalizedConfigValue(newValue)\n            }\n            self.logSecretUpdate(provider: .copilot, field: \"apiKey\", value: newValue)\n        }\n    }\n\n    func ensureCopilotAPITokenLoaded() {}\n}\n\nextension SettingsStore {\n    func copilotSettingsSnapshot() -> ProviderSettingsSnapshot.CopilotProviderSettings {\n        ProviderSettingsSnapshot.CopilotProviderSettings()\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Cursor/CursorLoginFlow.swift",
    "content": "import CodexBarCore\n\n@MainActor\nextension StatusItemController {\n    func runCursorLoginFlow() async {\n        let cursorRunner = CursorLoginRunner(browserDetection: self.store.browserDetection)\n        let phaseHandler: @Sendable (CursorLoginRunner.Phase) -> Void = { [weak self] phase in\n            Task { @MainActor in\n                switch phase {\n                case .loading, .waitingLogin:\n                    self?.loginPhase = .waitingBrowser\n                case .success, .failed:\n                    self?.loginPhase = .idle\n                }\n            }\n        }\n        let result = await cursorRunner.run(onPhaseChange: phaseHandler)\n        guard !Task.isCancelled else { return }\n        self.loginPhase = .idle\n        self.presentCursorLoginResult(result)\n        let outcome = self.describe(result.outcome)\n        self.loginLogger.info(\"Cursor login\", metadata: [\"outcome\": outcome])\n        if case .success = result.outcome {\n            self.postLoginNotification(for: .cursor)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Cursor/CursorProviderImplementation.swift",
    "content": "import CodexBarCore\nimport CodexBarMacroSupport\nimport Foundation\nimport SwiftUI\n\n@ProviderImplementationRegistration\nstruct CursorProviderImplementation: ProviderImplementation {\n    let id: UsageProvider = .cursor\n    let supportsLoginFlow: Bool = true\n\n    @MainActor\n    func presentation(context _: ProviderPresentationContext) -> ProviderPresentation {\n        ProviderPresentation { _ in \"web\" }\n    }\n\n    @MainActor\n    func observeSettings(_ settings: SettingsStore) {\n        _ = settings.cursorCookieSource\n        _ = settings.cursorCookieHeader\n    }\n\n    @MainActor\n    func settingsSnapshot(context: ProviderSettingsSnapshotContext) -> ProviderSettingsSnapshotContribution? {\n        .cursor(context.settings.cursorSettingsSnapshot(tokenOverride: context.tokenOverride))\n    }\n\n    @MainActor\n    func tokenAccountsVisibility(context: ProviderSettingsContext, support: TokenAccountSupport) -> Bool {\n        guard support.requiresManualCookieSource else { return true }\n        if !context.settings.tokenAccounts(for: context.provider).isEmpty { return true }\n        return context.settings.cursorCookieSource == .manual\n    }\n\n    @MainActor\n    func applyTokenAccountCookieSource(settings: SettingsStore) {\n        if settings.cursorCookieSource != .manual {\n            settings.cursorCookieSource = .manual\n        }\n    }\n\n    @MainActor\n    func settingsPickers(context: ProviderSettingsContext) -> [ProviderSettingsPickerDescriptor] {\n        let cookieBinding = Binding(\n            get: { context.settings.cursorCookieSource.rawValue },\n            set: { raw in\n                context.settings.cursorCookieSource = ProviderCookieSource(rawValue: raw) ?? .auto\n            })\n        let cookieOptions = ProviderCookieSourceUI.options(\n            allowsOff: false,\n            keychainDisabled: context.settings.debugDisableKeychainAccess)\n\n        let cookieSubtitle: () -> String? = {\n            ProviderCookieSourceUI.subtitle(\n                source: context.settings.cursorCookieSource,\n                keychainDisabled: context.settings.debugDisableKeychainAccess,\n                auto: \"Automatic imports browser cookies or stored sessions.\",\n                manual: \"Paste a Cookie header from a cursor.com request.\",\n                off: \"Cursor cookies are disabled.\")\n        }\n\n        return [\n            ProviderSettingsPickerDescriptor(\n                id: \"cursor-cookie-source\",\n                title: \"Cookie source\",\n                subtitle: \"Automatic imports browser cookies or stored sessions.\",\n                dynamicSubtitle: cookieSubtitle,\n                binding: cookieBinding,\n                options: cookieOptions,\n                isVisible: nil,\n                onChange: nil,\n                trailingText: {\n                    guard let entry = CookieHeaderCache.load(provider: .cursor) else { return nil }\n                    let when = entry.storedAt.relativeDescription()\n                    return \"Cached: \\(entry.sourceLabel) • \\(when)\"\n                }),\n        ]\n    }\n\n    @MainActor\n    func settingsFields(context: ProviderSettingsContext) -> [ProviderSettingsFieldDescriptor] {\n        _ = context\n        return []\n    }\n\n    @MainActor\n    func runLoginFlow(context: ProviderLoginContext) async -> Bool {\n        await context.controller.runCursorLoginFlow()\n        return true\n    }\n\n    @MainActor\n    func appendUsageMenuEntries(context: ProviderMenuUsageContext, entries: inout [ProviderMenuEntry]) {\n        guard let cost = context.snapshot?.providerCost, cost.currencyCode != \"Quota\" else { return }\n        let used = UsageFormatter.currencyString(cost.used, currencyCode: cost.currencyCode)\n        if cost.limit > 0 {\n            let limitStr = UsageFormatter.currencyString(cost.limit, currencyCode: cost.currencyCode)\n            entries.append(.text(\"On-Demand: \\(used) / \\(limitStr)\", .primary))\n        } else {\n            entries.append(.text(\"On-Demand: \\(used)\", .primary))\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Cursor/CursorSettingsStore.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nextension SettingsStore {\n    var cursorCookieHeader: String {\n        get { self.configSnapshot.providerConfig(for: .cursor)?.sanitizedCookieHeader ?? \"\" }\n        set {\n            self.updateProviderConfig(provider: .cursor) { entry in\n                entry.cookieHeader = self.normalizedConfigValue(newValue)\n            }\n            self.logSecretUpdate(provider: .cursor, field: \"cookieHeader\", value: newValue)\n        }\n    }\n\n    var cursorCookieSource: ProviderCookieSource {\n        get { self.resolvedCookieSource(provider: .cursor, fallback: .auto) }\n        set {\n            self.updateProviderConfig(provider: .cursor) { entry in\n                entry.cookieSource = newValue\n            }\n            self.logProviderModeChange(provider: .cursor, field: \"cookieSource\", value: newValue.rawValue)\n        }\n    }\n\n    func ensureCursorCookieLoaded() {}\n}\n\nextension SettingsStore {\n    func cursorSettingsSnapshot(tokenOverride: TokenAccountOverride?) -> ProviderSettingsSnapshot\n    .CursorProviderSettings {\n        ProviderSettingsSnapshot.CursorProviderSettings(\n            cookieSource: self.cursorSnapshotCookieSource(tokenOverride: tokenOverride),\n            manualCookieHeader: self.cursorSnapshotCookieHeader(tokenOverride: tokenOverride))\n    }\n\n    private func cursorSnapshotCookieHeader(tokenOverride: TokenAccountOverride?) -> String {\n        let fallback = self.cursorCookieHeader\n        guard let support = TokenAccountSupportCatalog.support(for: .cursor),\n              case .cookieHeader = support.injection\n        else {\n            return fallback\n        }\n        guard let account = ProviderTokenAccountSelection.selectedAccount(\n            provider: .cursor,\n            settings: self,\n            override: tokenOverride)\n        else {\n            return fallback\n        }\n        return TokenAccountSupportCatalog.normalizedCookieHeader(account.token, support: support)\n    }\n\n    private func cursorSnapshotCookieSource(tokenOverride: TokenAccountOverride?) -> ProviderCookieSource {\n        let fallback = self.cursorCookieSource\n        guard let support = TokenAccountSupportCatalog.support(for: .cursor),\n              support.requiresManualCookieSource\n        else {\n            return fallback\n        }\n        if self.tokenAccounts(for: .cursor).isEmpty { return fallback }\n        return .manual\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Factory/FactoryLoginFlow.swift",
    "content": "import AppKit\nimport CodexBarCore\n\n@MainActor\nextension StatusItemController {\n    func runFactoryLoginFlow() async {\n        // Open Factory login page in default browser\n        if let url = URL(string: \"https://app.factory.ai\") {\n            NSWorkspace.shared.open(url)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Factory/FactoryProviderImplementation.swift",
    "content": "import CodexBarCore\nimport CodexBarMacroSupport\nimport Foundation\nimport SwiftUI\n\n@ProviderImplementationRegistration\nstruct FactoryProviderImplementation: ProviderImplementation {\n    let id: UsageProvider = .factory\n    let supportsLoginFlow: Bool = true\n\n    @MainActor\n    func observeSettings(_ settings: SettingsStore) {\n        _ = settings.factoryCookieSource\n        _ = settings.factoryCookieHeader\n    }\n\n    @MainActor\n    func settingsSnapshot(context: ProviderSettingsSnapshotContext) -> ProviderSettingsSnapshotContribution? {\n        .factory(context.settings.factorySettingsSnapshot(tokenOverride: context.tokenOverride))\n    }\n\n    @MainActor\n    func tokenAccountsVisibility(context: ProviderSettingsContext, support: TokenAccountSupport) -> Bool {\n        guard support.requiresManualCookieSource else { return true }\n        if !context.settings.tokenAccounts(for: context.provider).isEmpty { return true }\n        return context.settings.factoryCookieSource == .manual\n    }\n\n    @MainActor\n    func applyTokenAccountCookieSource(settings: SettingsStore) {\n        if settings.factoryCookieSource != .manual {\n            settings.factoryCookieSource = .manual\n        }\n    }\n\n    @MainActor\n    func settingsPickers(context: ProviderSettingsContext) -> [ProviderSettingsPickerDescriptor] {\n        let cookieBinding = Binding(\n            get: { context.settings.factoryCookieSource.rawValue },\n            set: { raw in\n                context.settings.factoryCookieSource = ProviderCookieSource(rawValue: raw) ?? .auto\n            })\n        let cookieOptions = ProviderCookieSourceUI.options(\n            allowsOff: false,\n            keychainDisabled: context.settings.debugDisableKeychainAccess)\n\n        let cookieSubtitle: () -> String? = {\n            ProviderCookieSourceUI.subtitle(\n                source: context.settings.factoryCookieSource,\n                keychainDisabled: context.settings.debugDisableKeychainAccess,\n                auto: \"Automatic imports browser cookies and WorkOS tokens.\",\n                manual: \"Paste a Cookie header from app.factory.ai.\",\n                off: \"Factory cookies are disabled.\")\n        }\n\n        return [\n            ProviderSettingsPickerDescriptor(\n                id: \"factory-cookie-source\",\n                title: \"Cookie source\",\n                subtitle: \"Automatic imports browser cookies and WorkOS tokens.\",\n                dynamicSubtitle: cookieSubtitle,\n                binding: cookieBinding,\n                options: cookieOptions,\n                isVisible: nil,\n                onChange: nil,\n                trailingText: {\n                    guard let entry = CookieHeaderCache.load(provider: .factory) else { return nil }\n                    let when = entry.storedAt.relativeDescription()\n                    return \"Cached: \\(entry.sourceLabel) • \\(when)\"\n                }),\n        ]\n    }\n\n    @MainActor\n    func settingsFields(context: ProviderSettingsContext) -> [ProviderSettingsFieldDescriptor] {\n        _ = context\n        return []\n    }\n\n    @MainActor\n    func runLoginFlow(context: ProviderLoginContext) async -> Bool {\n        await context.controller.runFactoryLoginFlow()\n        return true\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Factory/FactorySettingsStore.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nextension SettingsStore {\n    var factoryCookieHeader: String {\n        get { self.configSnapshot.providerConfig(for: .factory)?.sanitizedCookieHeader ?? \"\" }\n        set {\n            self.updateProviderConfig(provider: .factory) { entry in\n                entry.cookieHeader = self.normalizedConfigValue(newValue)\n            }\n            self.logSecretUpdate(provider: .factory, field: \"cookieHeader\", value: newValue)\n        }\n    }\n\n    var factoryCookieSource: ProviderCookieSource {\n        get { self.resolvedCookieSource(provider: .factory, fallback: .auto) }\n        set {\n            self.updateProviderConfig(provider: .factory) { entry in\n                entry.cookieSource = newValue\n            }\n            self.logProviderModeChange(provider: .factory, field: \"cookieSource\", value: newValue.rawValue)\n        }\n    }\n\n    func ensureFactoryCookieLoaded() {}\n}\n\nextension SettingsStore {\n    func factorySettingsSnapshot(tokenOverride: TokenAccountOverride?) -> ProviderSettingsSnapshot\n    .FactoryProviderSettings {\n        ProviderSettingsSnapshot.FactoryProviderSettings(\n            cookieSource: self.factorySnapshotCookieSource(tokenOverride: tokenOverride),\n            manualCookieHeader: self.factorySnapshotCookieHeader(tokenOverride: tokenOverride))\n    }\n\n    private func factorySnapshotCookieHeader(tokenOverride: TokenAccountOverride?) -> String {\n        let fallback = self.factoryCookieHeader\n        guard let support = TokenAccountSupportCatalog.support(for: .factory),\n              case .cookieHeader = support.injection\n        else {\n            return fallback\n        }\n        guard let account = ProviderTokenAccountSelection.selectedAccount(\n            provider: .factory,\n            settings: self,\n            override: tokenOverride)\n        else {\n            return fallback\n        }\n        return TokenAccountSupportCatalog.normalizedCookieHeader(account.token, support: support)\n    }\n\n    private func factorySnapshotCookieSource(tokenOverride: TokenAccountOverride?) -> ProviderCookieSource {\n        let fallback = self.factoryCookieSource\n        guard let support = TokenAccountSupportCatalog.support(for: .factory),\n              support.requiresManualCookieSource\n        else {\n            return fallback\n        }\n        if self.tokenAccounts(for: .factory).isEmpty { return fallback }\n        return .manual\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Gemini/GeminiLoginFlow.swift",
    "content": "import CodexBarCore\n\n@MainActor\nextension StatusItemController {\n    func runGeminiLoginFlow() async {\n        let store = self.store\n        let result = await GeminiLoginRunner.run {\n            Task { @MainActor in\n                await store.refresh()\n                CodexBarLog.logger(LogCategories.login).info(\"Auto-refreshed after Gemini auth\")\n            }\n        }\n        guard !Task.isCancelled else { return }\n        self.loginPhase = .idle\n        self.presentGeminiLoginResult(result)\n        let outcome = self.describe(result.outcome)\n        self.loginLogger.info(\"Gemini login\", metadata: [\"outcome\": outcome])\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Gemini/GeminiProviderImplementation.swift",
    "content": "import CodexBarCore\nimport CodexBarMacroSupport\nimport Foundation\n\n@ProviderImplementationRegistration\nstruct GeminiProviderImplementation: ProviderImplementation {\n    let id: UsageProvider = .gemini\n    let supportsLoginFlow: Bool = true\n\n    @MainActor\n    func runLoginFlow(context: ProviderLoginContext) async -> Bool {\n        await context.controller.runGeminiLoginFlow()\n        return false\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/JetBrains/JetBrainsLoginFlow.swift",
    "content": "import CodexBarCore\n\n@MainActor\nextension StatusItemController {\n    func runJetBrainsLoginFlow() async {\n        self.loginPhase = .idle\n        let detectedIDEs = JetBrainsIDEDetector.detectInstalledIDEs(includeMissingQuota: true)\n        if detectedIDEs.isEmpty {\n            let message = [\n                \"Install a JetBrains IDE with AI Assistant enabled, then refresh CodexBar.\",\n                \"Alternatively, set a custom path in Settings.\",\n            ].joined(separator: \" \")\n            self.presentLoginAlert(\n                title: \"No JetBrains IDE detected\",\n                message: message)\n        } else {\n            let ideNames = detectedIDEs.prefix(3).map(\\.displayName).joined(separator: \", \")\n            let hasQuotaFile = !JetBrainsIDEDetector.detectInstalledIDEs().isEmpty\n            let message = hasQuotaFile\n                ? \"Detected: \\(ideNames). Select your preferred IDE in Settings, then refresh CodexBar.\"\n                : \"Detected: \\(ideNames). Use AI Assistant once to generate quota data, then refresh CodexBar.\"\n            self.presentLoginAlert(\n                title: \"JetBrains AI is ready\",\n                message: message)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/JetBrains/JetBrainsProviderImplementation.swift",
    "content": "import CodexBarCore\nimport CodexBarMacroSupport\nimport Foundation\nimport SwiftUI\n\n@ProviderImplementationRegistration\nstruct JetBrainsProviderImplementation: ProviderImplementation {\n    let id: UsageProvider = .jetbrains\n\n    @MainActor\n    func settingsSnapshot(context: ProviderSettingsSnapshotContext) -> ProviderSettingsSnapshotContribution? {\n        _ = context\n        return .jetbrains(context.settings.jetbrainsSettingsSnapshot())\n    }\n\n    @MainActor\n    func settingsPickers(context: ProviderSettingsContext) -> [ProviderSettingsPickerDescriptor] {\n        let detectedIDEs = JetBrainsIDEDetector.detectInstalledIDEs(includeMissingQuota: true)\n        guard !detectedIDEs.isEmpty else { return [] }\n\n        var options: [ProviderSettingsPickerOption] = [\n            ProviderSettingsPickerOption(id: \"\", title: \"Auto-detect\"),\n        ]\n        for ide in detectedIDEs {\n            options.append(ProviderSettingsPickerOption(id: ide.basePath, title: ide.displayName))\n        }\n\n        return [\n            ProviderSettingsPickerDescriptor(\n                id: \"jetbrains.ide\",\n                title: \"JetBrains IDE\",\n                subtitle: \"Select the IDE to monitor\",\n                binding: context.stringBinding(\\.jetbrainsIDEBasePath),\n                options: options,\n                isVisible: nil,\n                onChange: nil,\n                trailingText: {\n                    if context.settings.jetbrainsIDEBasePath.isEmpty {\n                        if let latest = JetBrainsIDEDetector.detectLatestIDE() {\n                            return latest.displayName\n                        }\n                    }\n                    return nil\n                }),\n        ]\n    }\n\n    @MainActor\n    func settingsFields(context: ProviderSettingsContext) -> [ProviderSettingsFieldDescriptor] {\n        [\n            ProviderSettingsFieldDescriptor(\n                id: \"jetbrains.customPath\",\n                title: \"Custom Path\",\n                subtitle: \"Override auto-detection with a custom IDE base path\",\n                kind: .plain,\n                placeholder: \"~/Library/Application Support/JetBrains/IntelliJIdea2024.3\",\n                binding: context.stringBinding(\\.jetbrainsIDEBasePath),\n                actions: [],\n                isVisible: {\n                    let detectedIDEs = JetBrainsIDEDetector.detectInstalledIDEs()\n                    return detectedIDEs.isEmpty || !context.settings.jetbrainsIDEBasePath.isEmpty\n                },\n                onActivate: nil),\n        ]\n    }\n\n    @MainActor\n    func runLoginFlow(context: ProviderLoginContext) async -> Bool {\n        await context.controller.runJetBrainsLoginFlow()\n        return false\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/JetBrains/JetBrainsSettingsStore.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nextension SettingsStore {\n    func jetbrainsSettingsSnapshot() -> ProviderSettingsSnapshot.JetBrainsProviderSettings {\n        ProviderSettingsSnapshot.JetBrainsProviderSettings(\n            ideBasePath: self.jetbrainsIDEBasePath.isEmpty ? nil : self.jetbrainsIDEBasePath)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Kilo/KiloProviderImplementation.swift",
    "content": "import AppKit\nimport CodexBarCore\nimport CodexBarMacroSupport\nimport Foundation\nimport SwiftUI\n\n@ProviderImplementationRegistration\nstruct KiloProviderImplementation: ProviderImplementation {\n    let id: UsageProvider = .kilo\n\n    @MainActor\n    func observeSettings(_ settings: SettingsStore) {\n        _ = settings.kiloUsageDataSource\n        _ = settings.kiloExtrasEnabled\n        _ = settings.kiloAPIToken\n    }\n\n    @MainActor\n    func isAvailable(context _: ProviderAvailabilityContext) -> Bool {\n        // Keep availability permissive to avoid main-thread auth-file I/O while still showing Kilo for auth.json-only\n        // setups. Fetch-time auth resolution remains authoritative (env first, then auth file fallback).\n        true\n    }\n\n    @MainActor\n    func settingsSnapshot(context: ProviderSettingsSnapshotContext) -> ProviderSettingsSnapshotContribution? {\n        .kilo(context.settings.kiloSettingsSnapshot(tokenOverride: context.tokenOverride))\n    }\n\n    @MainActor\n    func defaultSourceLabel(context: ProviderSourceLabelContext) -> String? {\n        context.settings.kiloUsageDataSource.rawValue\n    }\n\n    @MainActor\n    func sourceMode(context: ProviderSourceModeContext) -> ProviderSourceMode {\n        switch context.settings.kiloUsageDataSource {\n        case .auto: .auto\n        case .api: .api\n        case .cli: .cli\n        }\n    }\n\n    @MainActor\n    func settingsPickers(context: ProviderSettingsContext) -> [ProviderSettingsPickerDescriptor] {\n        let usageBinding = Binding(\n            get: { context.settings.kiloUsageDataSource.rawValue },\n            set: { raw in\n                context.settings.kiloUsageDataSource = KiloUsageDataSource(rawValue: raw) ?? .auto\n            })\n        let usageOptions = KiloUsageDataSource.allCases.map {\n            ProviderSettingsPickerOption(id: $0.rawValue, title: $0.displayName)\n        }\n        return [\n            ProviderSettingsPickerDescriptor(\n                id: \"kilo-usage-source\",\n                title: \"Usage source\",\n                subtitle: \"Auto uses API first, then falls back to CLI on auth failures.\",\n                binding: usageBinding,\n                options: usageOptions,\n                isVisible: nil,\n                onChange: nil,\n                trailingText: {\n                    guard context.settings.kiloUsageDataSource == .auto else { return nil }\n                    let label = context.store.sourceLabel(for: .kilo)\n                    return label == \"auto\" ? nil : label\n                }),\n        ]\n    }\n\n    @MainActor\n    func settingsFields(context: ProviderSettingsContext) -> [ProviderSettingsFieldDescriptor] {\n        [\n            ProviderSettingsFieldDescriptor(\n                id: \"kilo-api-key\",\n                title: \"API key\",\n                subtitle: \"Stored in ~/.codexbar/config.json. You can also provide KILO_API_KEY or \"\n                    + \"~/.local/share/kilo/auth.json (kilo.access).\",\n                kind: .secure,\n                placeholder: \"kilo_...\",\n                binding: context.stringBinding(\\.kiloAPIToken),\n                actions: [],\n                isVisible: nil,\n                onActivate: nil),\n        ]\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Kilo/KiloSettingsStore.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nextension SettingsStore {\n    var kiloUsageDataSource: KiloUsageDataSource {\n        get {\n            let source = self.configSnapshot.providerConfig(for: .kilo)?.source\n            return Self.kiloUsageDataSource(from: source)\n        }\n        set {\n            let source: ProviderSourceMode? = switch newValue {\n            case .auto: .auto\n            case .api: .api\n            case .cli: .cli\n            }\n            self.updateProviderConfig(provider: .kilo) { entry in\n                entry.source = source\n            }\n            self.logProviderModeChange(provider: .kilo, field: \"usageSource\", value: newValue.rawValue)\n        }\n    }\n\n    var kiloExtrasEnabled: Bool {\n        get {\n            guard self.kiloUsageDataSource == .auto else { return false }\n            return self.kiloExtrasEnabledRaw\n        }\n        set {\n            self.kiloExtrasEnabledRaw = newValue\n        }\n    }\n\n    var kiloAPIToken: String {\n        get { self.configSnapshot.providerConfig(for: .kilo)?.sanitizedAPIKey ?? \"\" }\n        set {\n            self.updateProviderConfig(provider: .kilo) { entry in\n                entry.apiKey = self.normalizedConfigValue(newValue)\n            }\n            self.logSecretUpdate(provider: .kilo, field: \"apiKey\", value: newValue)\n        }\n    }\n\n    private var kiloExtrasEnabledRaw: Bool {\n        get { self.configSnapshot.providerConfig(for: .kilo)?.extrasEnabled ?? false }\n        set {\n            self.updateProviderConfig(provider: .kilo) { entry in\n                entry.extrasEnabled = newValue\n            }\n            self.logProviderModeChange(\n                provider: .kilo,\n                field: \"extrasEnabled\",\n                value: newValue ? \"1\" : \"0\")\n        }\n    }\n}\n\nextension SettingsStore {\n    func kiloSettingsSnapshot(tokenOverride _: TokenAccountOverride?) -> ProviderSettingsSnapshot.KiloProviderSettings {\n        ProviderSettingsSnapshot.KiloProviderSettings(\n            usageDataSource: self.kiloUsageDataSource,\n            extrasEnabled: self.kiloExtrasEnabled)\n    }\n\n    private static func kiloUsageDataSource(from source: ProviderSourceMode?) -> KiloUsageDataSource {\n        guard let source else { return .auto }\n        switch source {\n        case .auto, .web, .oauth:\n            return .auto\n        case .api:\n            return .api\n        case .cli:\n            return .cli\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Kimi/KimiProviderImplementation.swift",
    "content": "import AppKit\nimport CodexBarCore\nimport CodexBarMacroSupport\nimport Foundation\nimport SwiftUI\n\n@ProviderImplementationRegistration\nstruct KimiProviderImplementation: ProviderImplementation {\n    let id: UsageProvider = .kimi\n\n    @MainActor\n    func presentation(context _: ProviderPresentationContext) -> ProviderPresentation {\n        ProviderPresentation { _ in \"web\" }\n    }\n\n    @MainActor\n    func observeSettings(_ settings: SettingsStore) {\n        _ = settings.kimiCookieSource\n        _ = settings.kimiManualCookieHeader\n    }\n\n    @MainActor\n    func settingsSnapshot(context: ProviderSettingsSnapshotContext) -> ProviderSettingsSnapshotContribution? {\n        .kimi(context.settings.kimiSettingsSnapshot(tokenOverride: context.tokenOverride))\n    }\n\n    @MainActor\n    func settingsPickers(context: ProviderSettingsContext) -> [ProviderSettingsPickerDescriptor] {\n        let cookieBinding = Binding(\n            get: { context.settings.kimiCookieSource.rawValue },\n            set: { raw in\n                context.settings.kimiCookieSource = ProviderCookieSource(rawValue: raw) ?? .auto\n            })\n        let options = ProviderCookieSourceUI.options(\n            allowsOff: true,\n            keychainDisabled: context.settings.debugDisableKeychainAccess)\n\n        let subtitle: () -> String? = {\n            ProviderCookieSourceUI.subtitle(\n                source: context.settings.kimiCookieSource,\n                keychainDisabled: context.settings.debugDisableKeychainAccess,\n                auto: \"Automatic imports browser cookies.\",\n                manual: \"Paste a cookie header or the kimi-auth token value.\",\n                off: \"Kimi cookies are disabled.\")\n        }\n\n        return [\n            ProviderSettingsPickerDescriptor(\n                id: \"kimi-cookie-source\",\n                title: \"Cookie source\",\n                subtitle: \"Automatic imports browser cookies.\",\n                dynamicSubtitle: subtitle,\n                binding: cookieBinding,\n                options: options,\n                isVisible: nil,\n                onChange: nil),\n        ]\n    }\n\n    @MainActor\n    func settingsFields(context: ProviderSettingsContext) -> [ProviderSettingsFieldDescriptor] {\n        [\n            ProviderSettingsFieldDescriptor(\n                id: \"kimi-cookie\",\n                title: \"\",\n                subtitle: \"\",\n                kind: .secure,\n                placeholder: \"Cookie: \\u{2026}\\n\\nor paste the kimi-auth token value\",\n                binding: context.stringBinding(\\.kimiManualCookieHeader),\n                actions: [\n                    ProviderSettingsActionDescriptor(\n                        id: \"kimi-open-console\",\n                        title: \"Open Console\",\n                        style: .link,\n                        isVisible: nil,\n                        perform: {\n                            if let url = URL(string: \"https://www.kimi.com/code/console\") {\n                                NSWorkspace.shared.open(url)\n                            }\n                        }),\n                ],\n                isVisible: { context.settings.kimiCookieSource == .manual },\n                onActivate: { context.settings.ensureKimiAuthTokenLoaded() }),\n        ]\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Kimi/KimiSettingsStore.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nextension SettingsStore {\n    var kimiManualCookieHeader: String {\n        get { self.configSnapshot.providerConfig(for: .kimi)?.sanitizedCookieHeader ?? \"\" }\n        set {\n            self.updateProviderConfig(provider: .kimi) { entry in\n                entry.cookieHeader = self.normalizedConfigValue(newValue)\n            }\n            self.logSecretUpdate(provider: .kimi, field: \"cookieHeader\", value: newValue)\n        }\n    }\n\n    var kimiCookieSource: ProviderCookieSource {\n        get { self.resolvedCookieSource(provider: .kimi, fallback: .auto) }\n        set {\n            self.updateProviderConfig(provider: .kimi) { entry in\n                entry.cookieSource = newValue\n            }\n            self.logProviderModeChange(provider: .kimi, field: \"cookieSource\", value: newValue.rawValue)\n        }\n    }\n\n    func ensureKimiAuthTokenLoaded() {}\n}\n\nextension SettingsStore {\n    func kimiSettingsSnapshot(tokenOverride: TokenAccountOverride?) -> ProviderSettingsSnapshot.KimiProviderSettings {\n        _ = tokenOverride\n        self.ensureKimiAuthTokenLoaded()\n        return ProviderSettingsSnapshot.KimiProviderSettings(\n            cookieSource: self.kimiCookieSource,\n            manualCookieHeader: self.kimiManualCookieHeader)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/KimiK2/KimiK2ProviderImplementation.swift",
    "content": "import AppKit\nimport CodexBarCore\nimport CodexBarMacroSupport\nimport Foundation\n\n@ProviderImplementationRegistration\nstruct KimiK2ProviderImplementation: ProviderImplementation {\n    let id: UsageProvider = .kimik2\n\n    @MainActor\n    func observeSettings(_ settings: SettingsStore) {\n        _ = settings.kimiK2APIToken\n    }\n\n    @MainActor\n    func settingsFields(context: ProviderSettingsContext) -> [ProviderSettingsFieldDescriptor] {\n        [\n            ProviderSettingsFieldDescriptor(\n                id: \"kimi-k2-api-token\",\n                title: \"API key\",\n                subtitle: \"Stored in ~/.codexbar/config.json. Generate one at kimi-k2.ai.\",\n                kind: .secure,\n                placeholder: \"Paste API key…\",\n                binding: context.stringBinding(\\.kimiK2APIToken),\n                actions: [\n                    ProviderSettingsActionDescriptor(\n                        id: \"kimi-k2-open-api-keys\",\n                        title: \"Open API Keys\",\n                        style: .link,\n                        isVisible: nil,\n                        perform: {\n                            if let url = URL(string: \"https://kimi-k2.ai/user-center/api-keys\") {\n                                NSWorkspace.shared.open(url)\n                            }\n                        }),\n                ],\n                isVisible: nil,\n                onActivate: { context.settings.ensureKimiK2APITokenLoaded() }),\n        ]\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/KimiK2/KimiK2SettingsStore.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nextension SettingsStore {\n    var kimiK2APIToken: String {\n        get { self.configSnapshot.providerConfig(for: .kimik2)?.sanitizedAPIKey ?? \"\" }\n        set {\n            self.updateProviderConfig(provider: .kimik2) { entry in\n                entry.apiKey = self.normalizedConfigValue(newValue)\n            }\n            self.logSecretUpdate(provider: .kimik2, field: \"apiKey\", value: newValue)\n        }\n    }\n\n    func ensureKimiK2APITokenLoaded() {}\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Kiro/KiroProviderImplementation.swift",
    "content": "import CodexBarCore\nimport CodexBarMacroSupport\nimport Foundation\n\n@ProviderImplementationRegistration\nstruct KiroProviderImplementation: ProviderImplementation {\n    let id: UsageProvider = .kiro\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/MiniMax/MiniMaxProviderImplementation.swift",
    "content": "import AppKit\nimport CodexBarCore\nimport CodexBarMacroSupport\nimport Foundation\nimport SwiftUI\n\n@ProviderImplementationRegistration\nstruct MiniMaxProviderImplementation: ProviderImplementation {\n    let id: UsageProvider = .minimax\n\n    @MainActor\n    func presentation(context _: ProviderPresentationContext) -> ProviderPresentation {\n        ProviderPresentation { context in\n            context.store.sourceLabel(for: context.provider)\n        }\n    }\n\n    @MainActor\n    func observeSettings(_ settings: SettingsStore) {\n        _ = settings.minimaxCookieSource\n        _ = settings.minimaxCookieHeader\n        _ = settings.minimaxAPIToken\n        _ = settings.minimaxAPIRegion\n    }\n\n    @MainActor\n    func settingsSnapshot(context: ProviderSettingsSnapshotContext) -> ProviderSettingsSnapshotContribution? {\n        .minimax(context.settings.minimaxSettingsSnapshot(tokenOverride: context.tokenOverride))\n    }\n\n    @MainActor\n    func tokenAccountsVisibility(context: ProviderSettingsContext, support: TokenAccountSupport) -> Bool {\n        guard support.requiresManualCookieSource else { return true }\n        if !context.settings.tokenAccounts(for: context.provider).isEmpty { return true }\n        context.settings.ensureMiniMaxAPITokenLoaded()\n        if context.settings.minimaxAuthMode().usesAPIToken { return false }\n        return context.settings.minimaxCookieSource == .manual\n    }\n\n    @MainActor\n    func applyTokenAccountCookieSource(settings: SettingsStore) {\n        if settings.minimaxCookieSource != .manual {\n            settings.minimaxCookieSource = .manual\n        }\n    }\n\n    @MainActor\n    func settingsPickers(context: ProviderSettingsContext) -> [ProviderSettingsPickerDescriptor] {\n        context.settings.ensureMiniMaxAPITokenLoaded()\n        let authMode: () -> MiniMaxAuthMode = {\n            context.settings.minimaxAuthMode()\n        }\n\n        let cookieBinding = Binding(\n            get: { context.settings.minimaxCookieSource.rawValue },\n            set: { raw in\n                context.settings.minimaxCookieSource = ProviderCookieSource(rawValue: raw) ?? .auto\n            })\n        let cookieOptions = ProviderCookieSourceUI.options(\n            allowsOff: false,\n            keychainDisabled: context.settings.debugDisableKeychainAccess)\n\n        let cookieSubtitle: () -> String? = {\n            ProviderCookieSourceUI.subtitle(\n                source: context.settings.minimaxCookieSource,\n                keychainDisabled: context.settings.debugDisableKeychainAccess,\n                auto: \"Automatic imports browser cookies and local storage tokens.\",\n                manual: \"Paste a Cookie header or cURL capture from the Coding Plan page.\",\n                off: \"MiniMax cookies are disabled.\")\n        }\n\n        let regionBinding = Binding(\n            get: { context.settings.minimaxAPIRegion.rawValue },\n            set: { raw in\n                context.settings.minimaxAPIRegion = MiniMaxAPIRegion(rawValue: raw) ?? .global\n            })\n        let regionOptions = MiniMaxAPIRegion.allCases.map {\n            ProviderSettingsPickerOption(id: $0.rawValue, title: $0.displayName)\n        }\n\n        return [\n            ProviderSettingsPickerDescriptor(\n                id: \"minimax-cookie-source\",\n                title: \"Cookie source\",\n                subtitle: \"Automatic imports browser cookies and local storage tokens.\",\n                dynamicSubtitle: cookieSubtitle,\n                binding: cookieBinding,\n                options: cookieOptions,\n                isVisible: { authMode().allowsCookies },\n                onChange: nil,\n                trailingText: {\n                    guard let entry = CookieHeaderCache.load(provider: .minimax) else { return nil }\n                    let when = entry.storedAt.relativeDescription()\n                    return \"Cached: \\(entry.sourceLabel) • \\(when)\"\n                }),\n            ProviderSettingsPickerDescriptor(\n                id: \"minimax-region\",\n                title: \"API region\",\n                subtitle: \"Choose the MiniMax host (global .io or China mainland .com).\",\n                binding: regionBinding,\n                options: regionOptions,\n                isVisible: nil,\n                onChange: nil),\n        ]\n    }\n\n    @MainActor\n    func settingsFields(context: ProviderSettingsContext) -> [ProviderSettingsFieldDescriptor] {\n        context.settings.ensureMiniMaxAPITokenLoaded()\n        let authMode: () -> MiniMaxAuthMode = {\n            context.settings.minimaxAuthMode()\n        }\n\n        return [\n            ProviderSettingsFieldDescriptor(\n                id: \"minimax-api-token\",\n                title: \"API token\",\n                subtitle: \"Stored in ~/.codexbar/config.json. Paste your MiniMax API key.\",\n                kind: .secure,\n                placeholder: \"Paste API token…\",\n                binding: context.stringBinding(\\.minimaxAPIToken),\n                actions: [\n                    ProviderSettingsActionDescriptor(\n                        id: \"minimax-open-dashboard\",\n                        title: \"Open Coding Plan\",\n                        style: .link,\n                        isVisible: nil,\n                        perform: {\n                            NSWorkspace.shared.open(context.settings.minimaxAPIRegion.codingPlanURL)\n                        }),\n                ],\n                isVisible: nil,\n                onActivate: { context.settings.ensureMiniMaxAPITokenLoaded() }),\n            ProviderSettingsFieldDescriptor(\n                id: \"minimax-cookie\",\n                title: \"Cookie header\",\n                subtitle: \"\",\n                kind: .secure,\n                placeholder: \"Cookie: …\",\n                binding: context.stringBinding(\\.minimaxCookieHeader),\n                actions: [\n                    ProviderSettingsActionDescriptor(\n                        id: \"minimax-open-dashboard-cookie\",\n                        title: \"Open Coding Plan\",\n                        style: .link,\n                        isVisible: nil,\n                        perform: {\n                            NSWorkspace.shared.open(context.settings.minimaxAPIRegion.codingPlanURL)\n                        }),\n                ],\n                isVisible: {\n                    authMode().allowsCookies && context.settings.minimaxCookieSource == .manual\n                },\n                onActivate: { context.settings.ensureMiniMaxCookieLoaded() }),\n        ]\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/MiniMax/MiniMaxSettingsStore.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nextension SettingsStore {\n    var minimaxAPIRegion: MiniMaxAPIRegion {\n        get {\n            let raw = self.configSnapshot.providerConfig(for: .minimax)?.region\n            return MiniMaxAPIRegion(rawValue: raw ?? \"\") ?? .global\n        }\n        set {\n            self.updateProviderConfig(provider: .minimax) { entry in\n                entry.region = newValue.rawValue\n            }\n        }\n    }\n\n    var minimaxCookieHeader: String {\n        get { self.configSnapshot.providerConfig(for: .minimax)?.sanitizedCookieHeader ?? \"\" }\n        set {\n            self.updateProviderConfig(provider: .minimax) { entry in\n                entry.cookieHeader = self.normalizedConfigValue(newValue)\n            }\n            self.logSecretUpdate(provider: .minimax, field: \"cookieHeader\", value: newValue)\n        }\n    }\n\n    var minimaxAPIToken: String {\n        get { self.configSnapshot.providerConfig(for: .minimax)?.sanitizedAPIKey ?? \"\" }\n        set {\n            self.updateProviderConfig(provider: .minimax) { entry in\n                entry.apiKey = self.normalizedConfigValue(newValue)\n            }\n            self.logSecretUpdate(provider: .minimax, field: \"apiKey\", value: newValue)\n        }\n    }\n\n    var minimaxCookieSource: ProviderCookieSource {\n        get { self.resolvedCookieSource(provider: .minimax, fallback: .auto) }\n        set {\n            self.updateProviderConfig(provider: .minimax) { entry in\n                entry.cookieSource = newValue\n            }\n            self.logProviderModeChange(provider: .minimax, field: \"cookieSource\", value: newValue.rawValue)\n        }\n    }\n\n    func ensureMiniMaxCookieLoaded() {}\n\n    func ensureMiniMaxAPITokenLoaded() {}\n\n    func minimaxAuthMode(\n        environment: [String: String] = ProcessInfo.processInfo.environment) -> MiniMaxAuthMode\n    {\n        let apiToken = MiniMaxAPISettingsReader.apiToken(environment: environment) ?? self.minimaxAPIToken\n        let cookieHeader = MiniMaxSettingsReader.cookieHeader(environment: environment) ?? self.minimaxCookieHeader\n        return MiniMaxAuthMode.resolve(apiToken: apiToken, cookieHeader: cookieHeader)\n    }\n}\n\nextension SettingsStore {\n    func minimaxSettingsSnapshot(tokenOverride: TokenAccountOverride?) -> ProviderSettingsSnapshot\n    .MiniMaxProviderSettings {\n        ProviderSettingsSnapshot.MiniMaxProviderSettings(\n            cookieSource: self.minimaxSnapshotCookieSource(tokenOverride: tokenOverride),\n            manualCookieHeader: self.minimaxSnapshotCookieHeader(tokenOverride: tokenOverride),\n            apiRegion: self.minimaxAPIRegion)\n    }\n\n    private func minimaxSnapshotCookieHeader(tokenOverride: TokenAccountOverride?) -> String {\n        let fallback = self.minimaxCookieHeader\n        guard let support = TokenAccountSupportCatalog.support(for: .minimax),\n              case .cookieHeader = support.injection\n        else {\n            return fallback\n        }\n        guard let account = ProviderTokenAccountSelection.selectedAccount(\n            provider: .minimax,\n            settings: self,\n            override: tokenOverride)\n        else {\n            return fallback\n        }\n        return TokenAccountSupportCatalog.normalizedCookieHeader(account.token, support: support)\n    }\n\n    private func minimaxSnapshotCookieSource(tokenOverride: TokenAccountOverride?) -> ProviderCookieSource {\n        let fallback = self.minimaxCookieSource\n        guard let support = TokenAccountSupportCatalog.support(for: .minimax),\n              support.requiresManualCookieSource\n        else {\n            return fallback\n        }\n        if self.tokenAccounts(for: .minimax).isEmpty { return fallback }\n        return .manual\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Ollama/OllamaProviderImplementation.swift",
    "content": "import AppKit\nimport CodexBarCore\nimport CodexBarMacroSupport\nimport Foundation\nimport SwiftUI\n\n@ProviderImplementationRegistration\nstruct OllamaProviderImplementation: ProviderImplementation {\n    let id: UsageProvider = .ollama\n\n    @MainActor\n    func observeSettings(_ settings: SettingsStore) {\n        _ = settings.ollamaCookieSource\n        _ = settings.ollamaCookieHeader\n    }\n\n    @MainActor\n    func settingsSnapshot(context: ProviderSettingsSnapshotContext) -> ProviderSettingsSnapshotContribution? {\n        .ollama(context.settings.ollamaSettingsSnapshot(tokenOverride: context.tokenOverride))\n    }\n\n    @MainActor\n    func tokenAccountsVisibility(context: ProviderSettingsContext, support: TokenAccountSupport) -> Bool {\n        guard support.requiresManualCookieSource else { return true }\n        if !context.settings.tokenAccounts(for: context.provider).isEmpty { return true }\n        return context.settings.ollamaCookieSource == .manual\n    }\n\n    @MainActor\n    func applyTokenAccountCookieSource(settings: SettingsStore) {\n        if settings.ollamaCookieSource != .manual {\n            settings.ollamaCookieSource = .manual\n        }\n    }\n\n    @MainActor\n    func settingsPickers(context: ProviderSettingsContext) -> [ProviderSettingsPickerDescriptor] {\n        let cookieBinding = Binding(\n            get: { context.settings.ollamaCookieSource.rawValue },\n            set: { raw in\n                context.settings.ollamaCookieSource = ProviderCookieSource(rawValue: raw) ?? .auto\n            })\n        let cookieOptions = ProviderCookieSourceUI.options(\n            allowsOff: false,\n            keychainDisabled: context.settings.debugDisableKeychainAccess)\n\n        let cookieSubtitle: () -> String? = {\n            ProviderCookieSourceUI.subtitle(\n                source: context.settings.ollamaCookieSource,\n                keychainDisabled: context.settings.debugDisableKeychainAccess,\n                auto: \"Automatic imports browser cookies.\",\n                manual: \"Paste a Cookie header or cURL capture from Ollama settings.\",\n                off: \"Ollama cookies are disabled.\")\n        }\n\n        return [\n            ProviderSettingsPickerDescriptor(\n                id: \"ollama-cookie-source\",\n                title: \"Cookie source\",\n                subtitle: \"Automatic imports browser cookies.\",\n                dynamicSubtitle: cookieSubtitle,\n                binding: cookieBinding,\n                options: cookieOptions,\n                isVisible: nil,\n                onChange: nil),\n        ]\n    }\n\n    @MainActor\n    func settingsFields(context: ProviderSettingsContext) -> [ProviderSettingsFieldDescriptor] {\n        [\n            ProviderSettingsFieldDescriptor(\n                id: \"ollama-cookie\",\n                title: \"\",\n                subtitle: \"\",\n                kind: .secure,\n                placeholder: \"Cookie: …\",\n                binding: context.stringBinding(\\.ollamaCookieHeader),\n                actions: [\n                    ProviderSettingsActionDescriptor(\n                        id: \"ollama-open-settings\",\n                        title: \"Open Ollama Settings\",\n                        style: .link,\n                        isVisible: nil,\n                        perform: {\n                            if let url = URL(string: \"https://ollama.com/settings\") {\n                                NSWorkspace.shared.open(url)\n                            }\n                        }),\n                ],\n                isVisible: { context.settings.ollamaCookieSource == .manual },\n                onActivate: { context.settings.ensureOllamaCookieLoaded() }),\n        ]\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Ollama/OllamaSettingsStore.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nextension SettingsStore {\n    var ollamaCookieHeader: String {\n        get { self.configSnapshot.providerConfig(for: .ollama)?.sanitizedCookieHeader ?? \"\" }\n        set {\n            self.updateProviderConfig(provider: .ollama) { entry in\n                entry.cookieHeader = self.normalizedConfigValue(newValue)\n            }\n            self.logSecretUpdate(provider: .ollama, field: \"cookieHeader\", value: newValue)\n        }\n    }\n\n    var ollamaCookieSource: ProviderCookieSource {\n        get { self.resolvedCookieSource(provider: .ollama, fallback: .auto) }\n        set {\n            self.updateProviderConfig(provider: .ollama) { entry in\n                entry.cookieSource = newValue\n            }\n            self.logProviderModeChange(provider: .ollama, field: \"cookieSource\", value: newValue.rawValue)\n        }\n    }\n\n    func ensureOllamaCookieLoaded() {}\n}\n\nextension SettingsStore {\n    func ollamaSettingsSnapshot(tokenOverride: TokenAccountOverride?) -> ProviderSettingsSnapshot\n    .OllamaProviderSettings {\n        ProviderSettingsSnapshot.OllamaProviderSettings(\n            cookieSource: self.ollamaSnapshotCookieSource(tokenOverride: tokenOverride),\n            manualCookieHeader: self.ollamaSnapshotCookieHeader(tokenOverride: tokenOverride))\n    }\n\n    private func ollamaSnapshotCookieHeader(tokenOverride: TokenAccountOverride?) -> String {\n        let fallback = self.ollamaCookieHeader\n        guard let support = TokenAccountSupportCatalog.support(for: .ollama),\n              case .cookieHeader = support.injection\n        else {\n            return fallback\n        }\n        guard let account = ProviderTokenAccountSelection.selectedAccount(\n            provider: .ollama,\n            settings: self,\n            override: tokenOverride)\n        else {\n            return fallback\n        }\n        return TokenAccountSupportCatalog.normalizedCookieHeader(account.token, support: support)\n    }\n\n    private func ollamaSnapshotCookieSource(tokenOverride: TokenAccountOverride?) -> ProviderCookieSource {\n        let fallback = self.ollamaCookieSource\n        guard let support = TokenAccountSupportCatalog.support(for: .ollama),\n              support.requiresManualCookieSource\n        else {\n            return fallback\n        }\n        if self.tokenAccounts(for: .ollama).isEmpty { return fallback }\n        return .manual\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/OpenCode/OpenCodeProviderImplementation.swift",
    "content": "import AppKit\nimport CodexBarCore\nimport CodexBarMacroSupport\nimport Foundation\nimport SwiftUI\n\n@ProviderImplementationRegistration\nstruct OpenCodeProviderImplementation: ProviderImplementation {\n    let id: UsageProvider = .opencode\n\n    @MainActor\n    func presentation(context _: ProviderPresentationContext) -> ProviderPresentation {\n        ProviderPresentation { _ in \"web\" }\n    }\n\n    @MainActor\n    func observeSettings(_ settings: SettingsStore) {\n        _ = settings.opencodeCookieSource\n        _ = settings.opencodeCookieHeader\n        _ = settings.opencodeWorkspaceID\n    }\n\n    @MainActor\n    func settingsSnapshot(context: ProviderSettingsSnapshotContext) -> ProviderSettingsSnapshotContribution? {\n        .opencode(context.settings.opencodeSettingsSnapshot(tokenOverride: context.tokenOverride))\n    }\n\n    @MainActor\n    func tokenAccountsVisibility(context: ProviderSettingsContext, support: TokenAccountSupport) -> Bool {\n        guard support.requiresManualCookieSource else { return true }\n        if !context.settings.tokenAccounts(for: context.provider).isEmpty { return true }\n        return context.settings.opencodeCookieSource == .manual\n    }\n\n    @MainActor\n    func applyTokenAccountCookieSource(settings: SettingsStore) {\n        if settings.opencodeCookieSource != .manual {\n            settings.opencodeCookieSource = .manual\n        }\n    }\n\n    @MainActor\n    func settingsPickers(context: ProviderSettingsContext) -> [ProviderSettingsPickerDescriptor] {\n        let cookieBinding = Binding(\n            get: { context.settings.opencodeCookieSource.rawValue },\n            set: { raw in\n                context.settings.opencodeCookieSource = ProviderCookieSource(rawValue: raw) ?? .auto\n            })\n        let cookieOptions = ProviderCookieSourceUI.options(\n            allowsOff: false,\n            keychainDisabled: context.settings.debugDisableKeychainAccess)\n\n        let cookieSubtitle: () -> String? = {\n            ProviderCookieSourceUI.subtitle(\n                source: context.settings.opencodeCookieSource,\n                keychainDisabled: context.settings.debugDisableKeychainAccess,\n                auto: \"Automatic imports browser cookies from opencode.ai.\",\n                manual: \"Paste a Cookie header captured from the billing page.\",\n                off: \"OpenCode cookies are disabled.\")\n        }\n\n        return [\n            ProviderSettingsPickerDescriptor(\n                id: \"opencode-cookie-source\",\n                title: \"Cookie source\",\n                subtitle: \"Automatic imports browser cookies from opencode.ai.\",\n                dynamicSubtitle: cookieSubtitle,\n                binding: cookieBinding,\n                options: cookieOptions,\n                isVisible: nil,\n                onChange: nil,\n                trailingText: {\n                    guard let entry = CookieHeaderCache.load(provider: .opencode) else { return nil }\n                    let when = entry.storedAt.relativeDescription()\n                    return \"Cached: \\(entry.sourceLabel) • \\(when)\"\n                }),\n        ]\n    }\n\n    @MainActor\n    func settingsFields(context: ProviderSettingsContext) -> [ProviderSettingsFieldDescriptor] {\n        [\n            ProviderSettingsFieldDescriptor(\n                id: \"opencode-workspace-id\",\n                title: \"Workspace ID\",\n                subtitle: \"Optional override if workspace lookup fails.\",\n                kind: .plain,\n                placeholder: \"wrk_…\",\n                binding: context.stringBinding(\\.opencodeWorkspaceID),\n                actions: [],\n                isVisible: nil,\n                onActivate: nil),\n        ]\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/OpenCode/OpenCodeSettingsStore.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nextension SettingsStore {\n    var opencodeWorkspaceID: String {\n        get { self.configSnapshot.providerConfig(for: .opencode)?.workspaceID ?? \"\" }\n        set {\n            let trimmed = newValue.trimmingCharacters(in: .whitespacesAndNewlines)\n            let value = trimmed.isEmpty ? nil : trimmed\n            self.updateProviderConfig(provider: .opencode) { entry in\n                entry.workspaceID = value\n            }\n        }\n    }\n\n    var opencodeCookieHeader: String {\n        get { self.configSnapshot.providerConfig(for: .opencode)?.sanitizedCookieHeader ?? \"\" }\n        set {\n            self.updateProviderConfig(provider: .opencode) { entry in\n                entry.cookieHeader = self.normalizedConfigValue(newValue)\n            }\n            self.logSecretUpdate(provider: .opencode, field: \"cookieHeader\", value: newValue)\n        }\n    }\n\n    var opencodeCookieSource: ProviderCookieSource {\n        get { self.resolvedCookieSource(provider: .opencode, fallback: .auto) }\n        set {\n            self.updateProviderConfig(provider: .opencode) { entry in\n                entry.cookieSource = newValue\n            }\n            self.logProviderModeChange(provider: .opencode, field: \"cookieSource\", value: newValue.rawValue)\n        }\n    }\n\n    func ensureOpenCodeCookieLoaded() {}\n}\n\nextension SettingsStore {\n    func opencodeSettingsSnapshot(tokenOverride: TokenAccountOverride?) -> ProviderSettingsSnapshot\n    .OpenCodeProviderSettings {\n        ProviderSettingsSnapshot.OpenCodeProviderSettings(\n            cookieSource: self.opencodeSnapshotCookieSource(tokenOverride: tokenOverride),\n            manualCookieHeader: self.opencodeSnapshotCookieHeader(tokenOverride: tokenOverride),\n            workspaceID: self.opencodeWorkspaceID)\n    }\n\n    private func opencodeSnapshotCookieHeader(tokenOverride: TokenAccountOverride?) -> String {\n        let fallback = self.opencodeCookieHeader\n        guard let support = TokenAccountSupportCatalog.support(for: .opencode),\n              case .cookieHeader = support.injection\n        else {\n            return fallback\n        }\n        guard let account = ProviderTokenAccountSelection.selectedAccount(\n            provider: .opencode,\n            settings: self,\n            override: tokenOverride)\n        else {\n            return fallback\n        }\n        return TokenAccountSupportCatalog.normalizedCookieHeader(account.token, support: support)\n    }\n\n    private func opencodeSnapshotCookieSource(tokenOverride: TokenAccountOverride?) -> ProviderCookieSource {\n        let fallback = self.opencodeCookieSource\n        guard let support = TokenAccountSupportCatalog.support(for: .opencode),\n              support.requiresManualCookieSource\n        else {\n            return fallback\n        }\n        if self.tokenAccounts(for: .opencode).isEmpty { return fallback }\n        return .manual\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/OpenRouter/OpenRouterProviderImplementation.swift",
    "content": "import AppKit\nimport CodexBarCore\nimport CodexBarMacroSupport\nimport Foundation\nimport SwiftUI\n\n@ProviderImplementationRegistration\nstruct OpenRouterProviderImplementation: ProviderImplementation {\n    let id: UsageProvider = .openrouter\n\n    @MainActor\n    func presentation(context _: ProviderPresentationContext) -> ProviderPresentation {\n        ProviderPresentation { _ in \"api\" }\n    }\n\n    @MainActor\n    func observeSettings(_ settings: SettingsStore) {\n        _ = settings.openRouterAPIToken\n    }\n\n    @MainActor\n    func settingsSnapshot(context: ProviderSettingsSnapshotContext) -> ProviderSettingsSnapshotContribution? {\n        _ = context\n        return nil\n    }\n\n    @MainActor\n    func isAvailable(context: ProviderAvailabilityContext) -> Bool {\n        if OpenRouterSettingsReader.apiToken(environment: context.environment) != nil {\n            return true\n        }\n        return !context.settings.openRouterAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty\n    }\n\n    @MainActor\n    func settingsPickers(context _: ProviderSettingsContext) -> [ProviderSettingsPickerDescriptor] {\n        []\n    }\n\n    @MainActor\n    func settingsFields(context: ProviderSettingsContext) -> [ProviderSettingsFieldDescriptor] {\n        [\n            ProviderSettingsFieldDescriptor(\n                id: \"openrouter-api-key\",\n                title: \"API key\",\n                subtitle: \"Stored in ~/.codexbar/config.json. \"\n                    + \"Get your key from openrouter.ai/settings/keys and set a key spending limit \"\n                    + \"there to enable API key quota tracking.\",\n                kind: .secure,\n                placeholder: \"sk-or-v1-...\",\n                binding: context.stringBinding(\\.openRouterAPIToken),\n                actions: [],\n                isVisible: nil,\n                onActivate: nil),\n        ]\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/OpenRouter/OpenRouterSettingsStore.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nextension SettingsStore {\n    var openRouterAPIToken: String {\n        get { self.configSnapshot.providerConfig(for: .openrouter)?.sanitizedAPIKey ?? \"\" }\n        set {\n            self.updateProviderConfig(provider: .openrouter) { entry in\n                entry.apiKey = self.normalizedConfigValue(newValue)\n            }\n            self.logSecretUpdate(provider: .openrouter, field: \"apiKey\", value: newValue)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Shared/ProviderCatalog.swift",
    "content": "import CodexBarCore\n\n/// Source of truth for app-side provider implementations.\n///\n/// Keep provider registration centralized here. The rest of the app should *not* have to be updated when a new\n/// provider is added, aside from enum/metadata work in `CodexBarCore`.\nenum ProviderCatalog {\n    /// All provider implementations shipped in the app.\n    static let all: [any ProviderImplementation] = ProviderImplementationRegistry.all\n\n    /// Lookup for a single provider implementation.\n    static func implementation(for id: UsageProvider) -> (any ProviderImplementation)? {\n        ProviderImplementationRegistry.implementation(for: id)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Shared/ProviderContext.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nstruct ProviderPresentationContext {\n    let provider: UsageProvider\n    let settings: SettingsStore\n    let store: UsageStore\n    let metadata: ProviderMetadata\n}\n\nstruct ProviderAvailabilityContext {\n    let provider: UsageProvider\n    let settings: SettingsStore\n    let environment: [String: String]\n}\n\nstruct ProviderSourceLabelContext {\n    let provider: UsageProvider\n    let settings: SettingsStore\n    let store: UsageStore\n    let descriptor: ProviderDescriptor\n}\n\nstruct ProviderSourceModeContext {\n    let provider: UsageProvider\n    let settings: SettingsStore\n}\n\nstruct ProviderVersionContext {\n    let provider: UsageProvider\n    let browserDetection: BrowserDetection\n}\n\nstruct ProviderSettingsSnapshotContext {\n    let settings: SettingsStore\n    let tokenOverride: TokenAccountOverride?\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Shared/ProviderCookieSourceUI.swift",
    "content": "import CodexBarCore\n\nenum ProviderCookieSourceUI {\n    static let keychainDisabledPrefix =\n        \"Keychain access is disabled in Advanced, so browser cookie import is unavailable.\"\n\n    static func options(allowsOff: Bool, keychainDisabled: Bool) -> [ProviderSettingsPickerOption] {\n        var options: [ProviderSettingsPickerOption] = []\n        if !keychainDisabled {\n            options.append(ProviderSettingsPickerOption(\n                id: ProviderCookieSource.auto.rawValue,\n                title: ProviderCookieSource.auto.displayName))\n        }\n        options.append(ProviderSettingsPickerOption(\n            id: ProviderCookieSource.manual.rawValue,\n            title: ProviderCookieSource.manual.displayName))\n        if allowsOff {\n            options.append(ProviderSettingsPickerOption(\n                id: ProviderCookieSource.off.rawValue,\n                title: ProviderCookieSource.off.displayName))\n        }\n        return options\n    }\n\n    static func subtitle(\n        source: ProviderCookieSource,\n        keychainDisabled: Bool,\n        auto: String,\n        manual: String,\n        off: String) -> String\n    {\n        if keychainDisabled {\n            return source == .off ? off : \"\\(self.keychainDisabledPrefix) \\(manual)\"\n        }\n        switch source {\n        case .auto:\n            return auto\n        case .manual:\n            return manual\n        case .off:\n            return off\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Shared/ProviderImplementation.swift",
    "content": "import CodexBarCore\nimport Foundation\n\n/// App-side provider implementation.\n///\n/// Rules:\n/// - Provider implementations return *data/behavior descriptors*; the app owns UI.\n/// - Do not mix identity fields across providers (email/org/plan/loginMethod stays siloed).\nprotocol ProviderImplementation: Sendable {\n    var id: UsageProvider { get }\n    var supportsLoginFlow: Bool { get }\n\n    @MainActor\n    func presentation(context: ProviderPresentationContext) -> ProviderPresentation\n\n    @MainActor\n    func observeSettings(_ settings: SettingsStore)\n\n    @MainActor\n    func isAvailable(context: ProviderAvailabilityContext) -> Bool\n\n    @MainActor\n    func defaultSourceLabel(context: ProviderSourceLabelContext) -> String?\n\n    @MainActor\n    func decorateSourceLabel(context: ProviderSourceLabelContext, baseLabel: String) -> String\n\n    @MainActor\n    func sourceMode(context: ProviderSourceModeContext) -> ProviderSourceMode\n\n    func detectVersion(context: ProviderVersionContext) async -> String?\n\n    func makeRuntime() -> (any ProviderRuntime)?\n\n    /// Optional provider-specific settings toggles to render in the Providers pane.\n    ///\n    /// Important: Providers must not return custom SwiftUI views here. Only shared toggle/action descriptors.\n    @MainActor\n    func settingsToggles(context: ProviderSettingsContext) -> [ProviderSettingsToggleDescriptor]\n\n    /// Optional provider-specific settings fields to render in the Providers pane.\n    @MainActor\n    func settingsFields(context: ProviderSettingsContext) -> [ProviderSettingsFieldDescriptor]\n\n    /// Optional provider-specific settings pickers to render in the Providers pane.\n    @MainActor\n    func settingsPickers(context: ProviderSettingsContext) -> [ProviderSettingsPickerDescriptor]\n\n    /// Optional visibility gate for token account settings.\n    @MainActor\n    func tokenAccountsVisibility(context: ProviderSettingsContext, support: TokenAccountSupport) -> Bool\n\n    /// Optional provider-specific settings snapshot contribution.\n    @MainActor\n    func settingsSnapshot(context: ProviderSettingsSnapshotContext) -> ProviderSettingsSnapshotContribution?\n\n    /// Optional hook to update provider settings when token accounts change.\n    @MainActor\n    func applyTokenAccountCookieSource(settings: SettingsStore)\n\n    /// Optional provider-specific menu entries for the usage section.\n    @MainActor\n    func appendUsageMenuEntries(context: ProviderMenuUsageContext, entries: inout [ProviderMenuEntry])\n\n    /// Optional provider-specific menu entries for the actions section.\n    @MainActor\n    func appendActionMenuEntries(context: ProviderMenuActionContext, entries: inout [ProviderMenuEntry])\n\n    /// Optional override for the login/switch account menu action.\n    @MainActor\n    func loginMenuAction(context: ProviderMenuLoginContext) -> (label: String, action: MenuDescriptor.MenuAction)?\n\n    /// Optional provider-specific login flow. Returns whether to refresh after completion.\n    @MainActor\n    func runLoginFlow(context: ProviderLoginContext) async -> Bool\n}\n\nextension ProviderImplementation {\n    var supportsLoginFlow: Bool {\n        false\n    }\n\n    @MainActor\n    func presentation(context _: ProviderPresentationContext) -> ProviderPresentation {\n        ProviderPresentation(detailLine: ProviderPresentation.standardDetailLine)\n    }\n\n    @MainActor\n    func observeSettings(_ settings: SettingsStore) {\n        _ = settings\n    }\n\n    @MainActor\n    func isAvailable(context _: ProviderAvailabilityContext) -> Bool {\n        true\n    }\n\n    @MainActor\n    func defaultSourceLabel(context _: ProviderSourceLabelContext) -> String? {\n        nil\n    }\n\n    @MainActor\n    func decorateSourceLabel(context _: ProviderSourceLabelContext, baseLabel: String) -> String {\n        baseLabel\n    }\n\n    @MainActor\n    func sourceMode(context _: ProviderSourceModeContext) -> ProviderSourceMode {\n        .auto\n    }\n\n    func detectVersion(context: ProviderVersionContext) async -> String? {\n        let detector = ProviderDescriptorRegistry.descriptor(for: self.id).cli.versionDetector\n        return detector?(context.browserDetection)\n    }\n\n    func makeRuntime() -> (any ProviderRuntime)? {\n        nil\n    }\n\n    @MainActor\n    func settingsToggles(context _: ProviderSettingsContext) -> [ProviderSettingsToggleDescriptor] {\n        []\n    }\n\n    @MainActor\n    func settingsFields(context _: ProviderSettingsContext) -> [ProviderSettingsFieldDescriptor] {\n        []\n    }\n\n    @MainActor\n    func settingsPickers(context _: ProviderSettingsContext) -> [ProviderSettingsPickerDescriptor] {\n        []\n    }\n\n    @MainActor\n    func tokenAccountsVisibility(context: ProviderSettingsContext, support: TokenAccountSupport) -> Bool {\n        guard support.requiresManualCookieSource else { return true }\n        return !context.settings.tokenAccounts(for: context.provider).isEmpty\n    }\n\n    @MainActor\n    func settingsSnapshot(context _: ProviderSettingsSnapshotContext) -> ProviderSettingsSnapshotContribution? {\n        nil\n    }\n\n    @MainActor\n    func applyTokenAccountCookieSource(settings _: SettingsStore) {}\n\n    @MainActor\n    func appendUsageMenuEntries(context _: ProviderMenuUsageContext, entries _: inout [ProviderMenuEntry]) {}\n\n    @MainActor\n    func appendActionMenuEntries(context _: ProviderMenuActionContext, entries _: inout [ProviderMenuEntry]) {}\n\n    @MainActor\n    func loginMenuAction(context _: ProviderMenuLoginContext)\n        -> (label: String, action: MenuDescriptor.MenuAction)?\n    {\n        nil\n    }\n\n    @MainActor\n    func runLoginFlow(context _: ProviderLoginContext) async -> Bool {\n        false\n    }\n}\n\nstruct ProviderLoginContext {\n    unowned let controller: StatusItemController\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Shared/ProviderImplementationRegistry.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nenum ProviderImplementationRegistry {\n    private final class Store: @unchecked Sendable {\n        var ordered: [any ProviderImplementation] = []\n        var byID: [UsageProvider: any ProviderImplementation] = [:]\n    }\n\n    private static let lock = NSLock()\n    private static let store = Store()\n\n    // swiftlint:disable:next cyclomatic_complexity\n    private static func makeImplementation(for provider: UsageProvider) -> (any ProviderImplementation) {\n        switch provider {\n        case .codex: CodexProviderImplementation()\n        case .claude: ClaudeProviderImplementation()\n        case .cursor: CursorProviderImplementation()\n        case .opencode: OpenCodeProviderImplementation()\n        case .alibaba: AlibabaCodingPlanProviderImplementation()\n        case .factory: FactoryProviderImplementation()\n        case .gemini: GeminiProviderImplementation()\n        case .antigravity: AntigravityProviderImplementation()\n        case .copilot: CopilotProviderImplementation()\n        case .zai: ZaiProviderImplementation()\n        case .minimax: MiniMaxProviderImplementation()\n        case .kimi: KimiProviderImplementation()\n        case .kilo: KiloProviderImplementation()\n        case .kiro: KiroProviderImplementation()\n        case .vertexai: VertexAIProviderImplementation()\n        case .augment: AugmentProviderImplementation()\n        case .jetbrains: JetBrainsProviderImplementation()\n        case .kimik2: KimiK2ProviderImplementation()\n        case .amp: AmpProviderImplementation()\n        case .ollama: OllamaProviderImplementation()\n        case .synthetic: SyntheticProviderImplementation()\n        case .openrouter: OpenRouterProviderImplementation()\n        case .warp: WarpProviderImplementation()\n        }\n    }\n\n    private static let bootstrap: Void = {\n        for provider in UsageProvider.allCases {\n            _ = ProviderImplementationRegistry.register(makeImplementation(for: provider))\n        }\n    }()\n\n    private static func ensureBootstrapped() {\n        _ = self.bootstrap\n    }\n\n    @discardableResult\n    static func register(_ implementation: any ProviderImplementation) -> any ProviderImplementation {\n        self.lock.lock()\n        defer { self.lock.unlock() }\n        if self.store.byID[implementation.id] == nil {\n            self.store.ordered.append(implementation)\n        }\n        self.store.byID[implementation.id] = implementation\n        return implementation\n    }\n\n    static var all: [any ProviderImplementation] {\n        self.ensureBootstrapped()\n        self.lock.lock()\n        defer { self.lock.unlock() }\n        return self.store.ordered\n    }\n\n    static func implementation(for id: UsageProvider) -> (any ProviderImplementation)? {\n        self.ensureBootstrapped()\n        if let found = self.store.byID[id] { return found }\n        return self.all.first(where: { $0.id == id })\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Shared/ProviderLoginFlow.swift",
    "content": "import AppKit\nimport CodexBarCore\n\n@MainActor\nextension StatusItemController {\n    /// Runs the provider-specific login flow.\n    /// - Returns: Whether CodexBar should refresh after the flow completes.\n    func runLoginFlow(provider: UsageProvider) async -> Bool {\n        guard let impl = ProviderCatalog.implementation(for: provider) else { return false }\n        return await impl.runLoginFlow(context: ProviderLoginContext(controller: self))\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Shared/ProviderMenuContext.swift",
    "content": "import CodexBarCore\nimport Foundation\n\ntypealias ProviderMenuEntry = MenuDescriptor.Entry\n\nstruct ProviderMenuUsageContext {\n    let provider: UsageProvider\n    let store: UsageStore\n    let settings: SettingsStore\n    let metadata: ProviderMetadata\n    let snapshot: UsageSnapshot?\n}\n\nstruct ProviderMenuActionContext {\n    let provider: UsageProvider\n    let store: UsageStore\n    let settings: SettingsStore\n    let account: AccountInfo\n}\n\nstruct ProviderMenuLoginContext {\n    let provider: UsageProvider\n    let store: UsageStore\n    let settings: SettingsStore\n    let account: AccountInfo\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Shared/ProviderPresentation.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nstruct ProviderPresentation {\n    let detailLine: @MainActor (ProviderPresentationContext) -> String\n\n    @MainActor\n    static func standardDetailLine(context: ProviderPresentationContext) -> String {\n        let versionText = context.store.version(for: context.provider) ?? \"not detected\"\n        return \"\\(context.metadata.cliName) \\(versionText)\"\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Shared/ProviderRuntime.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nstruct ProviderRuntimeContext {\n    let provider: UsageProvider\n    let settings: SettingsStore\n    let store: UsageStore\n}\n\nenum ProviderRuntimeAction {\n    case forceSessionRefresh\n    case openAIWebAccessToggled(Bool)\n}\n\n@MainActor\nprotocol ProviderRuntime: AnyObject {\n    var id: UsageProvider { get }\n\n    func start(context: ProviderRuntimeContext)\n    func stop(context: ProviderRuntimeContext)\n    func settingsDidChange(context: ProviderRuntimeContext)\n    func providerDidRefresh(context: ProviderRuntimeContext, provider: UsageProvider)\n    func providerDidFail(context: ProviderRuntimeContext, provider: UsageProvider, error: Error)\n    func perform(action: ProviderRuntimeAction, context: ProviderRuntimeContext) async\n}\n\nextension ProviderRuntime {\n    func start(context _: ProviderRuntimeContext) {}\n    func stop(context _: ProviderRuntimeContext) {}\n    func settingsDidChange(context _: ProviderRuntimeContext) {}\n    func providerDidRefresh(context _: ProviderRuntimeContext, provider _: UsageProvider) {}\n    func providerDidFail(context _: ProviderRuntimeContext, provider _: UsageProvider, error _: Error) {}\n    func perform(action _: ProviderRuntimeAction, context _: ProviderRuntimeContext) async {}\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Shared/ProviderSettingsDescriptors.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport SwiftUI\n\n/// Settings UI context passed to provider implementations.\n///\n/// Providers use this to:\n/// - bind to `SettingsStore` values\n/// - read current provider state from `UsageStore`\n/// - surface transient status text (e.g. \"Importing cookies…\")\n/// - request a shared confirmation alert (no provider-specific UI)\n@MainActor\nstruct ProviderSettingsContext {\n    let provider: UsageProvider\n    let settings: SettingsStore\n    let store: UsageStore\n\n    let boolBinding: (ReferenceWritableKeyPath<SettingsStore, Bool>) -> Binding<Bool>\n    let stringBinding: (ReferenceWritableKeyPath<SettingsStore, String>) -> Binding<String>\n\n    let statusText: (String) -> String?\n    let setStatusText: (String, String?) -> Void\n\n    let lastAppActiveRunAt: (String) -> Date?\n    let setLastAppActiveRunAt: (String, Date?) -> Void\n\n    let requestConfirmation: (ProviderSettingsConfirmation) -> Void\n}\n\n/// Shared confirmation alert descriptor.\n///\n/// Providers can request confirmations (e.g. permission prompts) without supplying custom UI.\n@MainActor\nstruct ProviderSettingsConfirmation {\n    let title: String\n    let message: String\n    let confirmTitle: String\n    let onConfirm: () -> Void\n}\n\n/// Shared toggle descriptor rendered in the Providers settings pane.\n@MainActor\nstruct ProviderSettingsToggleDescriptor: Identifiable {\n    let id: String\n    let title: String\n    let subtitle: String\n    let binding: Binding<Bool>\n\n    /// Optional short status text shown under the toggle when enabled.\n    let statusText: (() -> String?)?\n\n    /// Optional actions shown under the toggle when enabled.\n    let actions: [ProviderSettingsActionDescriptor]\n\n    /// Optional runtime visibility gate.\n    let isVisible: (() -> Bool)?\n\n    /// Called whenever the toggle changes.\n    let onChange: ((_ enabled: Bool) async -> Void)?\n\n    /// Called when the app becomes active (used for \"retry after permission grant\" flows).\n    let onAppDidBecomeActive: (() async -> Void)?\n\n    /// Called when the view appears while the toggle is enabled.\n    let onAppearWhenEnabled: (() async -> Void)?\n}\n\n/// Shared text field descriptor rendered in the Providers settings pane.\n@MainActor\nstruct ProviderSettingsFieldDescriptor: Identifiable {\n    enum Kind {\n        case plain\n        case secure\n    }\n\n    let id: String\n    let title: String\n    let subtitle: String\n    let kind: Kind\n    let placeholder: String?\n    let binding: Binding<String>\n    let actions: [ProviderSettingsActionDescriptor]\n    let isVisible: (() -> Bool)?\n    let onActivate: (() -> Void)?\n}\n\n/// Shared token account descriptor rendered in the Providers settings pane.\n@MainActor\nstruct ProviderSettingsTokenAccountsDescriptor: Identifiable {\n    let id: String\n    let title: String\n    let subtitle: String\n    let placeholder: String\n    let provider: UsageProvider\n    let isVisible: (() -> Bool)?\n    let accounts: () -> [ProviderTokenAccount]\n    let activeIndex: () -> Int\n    let setActiveIndex: (Int) -> Void\n    let addAccount: (_ label: String, _ token: String) -> Void\n    let removeAccount: (_ accountID: UUID) -> Void\n    let openConfigFile: () -> Void\n    let reloadFromDisk: () -> Void\n}\n\n/// Shared picker descriptor rendered in the Providers settings pane.\n@MainActor\nstruct ProviderSettingsPickerDescriptor: Identifiable {\n    let id: String\n    let title: String\n    let subtitle: String\n    let dynamicSubtitle: (() -> String?)?\n    let binding: Binding<String>\n    let options: [ProviderSettingsPickerOption]\n    let isVisible: (() -> Bool)?\n    let isEnabled: (() -> Bool)?\n    let onChange: ((_ selection: String) async -> Void)?\n    let trailingText: (() -> String?)?\n\n    init(\n        id: String,\n        title: String,\n        subtitle: String,\n        dynamicSubtitle: (() -> String?)? = nil,\n        binding: Binding<String>,\n        options: [ProviderSettingsPickerOption],\n        isVisible: (() -> Bool)?,\n        isEnabled: (() -> Bool)? = nil,\n        onChange: ((_ selection: String) async -> Void)?,\n        trailingText: (() -> String?)? = nil)\n    {\n        self.id = id\n        self.title = title\n        self.subtitle = subtitle\n        self.dynamicSubtitle = dynamicSubtitle\n        self.binding = binding\n        self.options = options\n        self.isVisible = isVisible\n        self.isEnabled = isEnabled\n        self.onChange = onChange\n        self.trailingText = trailingText\n    }\n}\n\nstruct ProviderSettingsPickerOption: Identifiable {\n    let id: String\n    let title: String\n}\n\n/// Shared action descriptor rendered under a settings toggle.\n@MainActor\nstruct ProviderSettingsActionDescriptor: Identifiable {\n    enum Style {\n        case bordered\n        case link\n    }\n\n    let id: String\n    let title: String\n    let style: Style\n    let isVisible: (() -> Bool)?\n    let perform: () async -> Void\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Shared/ProviderTokenAccountSelection.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nstruct TokenAccountOverride {\n    let provider: UsageProvider\n    let account: ProviderTokenAccount\n}\n\nenum ProviderTokenAccountSelection {\n    @MainActor\n    static func selectedAccount(\n        provider: UsageProvider,\n        settings: SettingsStore,\n        override: TokenAccountOverride?) -> ProviderTokenAccount?\n    {\n        if let override, override.provider == provider { return override.account }\n        return settings.selectedTokenAccount(for: provider)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Shared/SystemSettingsLinks.swift",
    "content": "import AppKit\nimport Foundation\n\nenum SystemSettingsLinks {\n    /// Opens System Settings → Privacy & Security → Full Disk Access (best effort).\n    static func openFullDiskAccess() {\n        // Best-effort deep link. On older betas it sometimes opened the wrong pane; on modern macOS this is stable.\n        let urls: [URL] = [\n            URL(string: \"x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles\"),\n            URL(string: \"x-apple.systempreferences:com.apple.preference.security?Privacy\"),\n            URL(string: \"x-apple.systempreferences:com.apple.preference.security\"),\n        ].compactMap(\\.self)\n\n        for url in urls where NSWorkspace.shared.open(url) {\n            return\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Synthetic/SyntheticProviderImplementation.swift",
    "content": "import AppKit\nimport CodexBarCore\nimport CodexBarMacroSupport\nimport Foundation\n\n@ProviderImplementationRegistration\nstruct SyntheticProviderImplementation: ProviderImplementation {\n    let id: UsageProvider = .synthetic\n\n    @MainActor\n    func presentation(context _: ProviderPresentationContext) -> ProviderPresentation {\n        ProviderPresentation { _ in \"api\" }\n    }\n\n    @MainActor\n    func observeSettings(_ settings: SettingsStore) {\n        _ = settings.syntheticAPIToken\n    }\n\n    @MainActor\n    func isAvailable(context: ProviderAvailabilityContext) -> Bool {\n        if SyntheticSettingsReader.apiKey(environment: context.environment) != nil {\n            return true\n        }\n        context.settings.ensureSyntheticAPITokenLoaded()\n        return !context.settings.syntheticAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty\n    }\n\n    @MainActor\n    func settingsFields(context: ProviderSettingsContext) -> [ProviderSettingsFieldDescriptor] {\n        [\n            ProviderSettingsFieldDescriptor(\n                id: \"synthetic-api-key\",\n                title: \"API key\",\n                subtitle: \"Stored in ~/.codexbar/config.json. Paste the key from the Synthetic dashboard.\",\n                kind: .secure,\n                placeholder: \"Paste key…\",\n                binding: context.stringBinding(\\.syntheticAPIToken),\n                actions: [],\n                isVisible: nil,\n                onActivate: { context.settings.ensureSyntheticAPITokenLoaded() }),\n        ]\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Synthetic/SyntheticSettingsStore.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nextension SettingsStore {\n    var syntheticAPIToken: String {\n        get { self.configSnapshot.providerConfig(for: .synthetic)?.sanitizedAPIKey ?? \"\" }\n        set {\n            self.updateProviderConfig(provider: .synthetic) { entry in\n                entry.apiKey = self.normalizedConfigValue(newValue)\n            }\n            self.logSecretUpdate(provider: .synthetic, field: \"apiKey\", value: newValue)\n        }\n    }\n\n    func ensureSyntheticAPITokenLoaded() {}\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/VertexAI/VertexAILoginFlow.swift",
    "content": "import AppKit\nimport CodexBarCore\nimport Foundation\n\n@MainActor\nextension StatusItemController {\n    func runVertexAILoginFlow() async {\n        // Show alert with instructions\n        let alert = NSAlert()\n        alert.messageText = \"Vertex AI Login\"\n        alert.informativeText = \"\"\"\n        To use Vertex AI tracking, you need to authenticate with Google Cloud.\n\n        1. Open Terminal\n        2. Run: gcloud auth application-default login\n        3. Follow the browser prompts to sign in\n        4. Set your project: gcloud config set project PROJECT_ID\n\n        Would you like to open Terminal now?\n        \"\"\"\n        alert.alertStyle = .informational\n        alert.addButton(withTitle: \"Open Terminal\")\n        alert.addButton(withTitle: \"Cancel\")\n\n        let response = alert.runModal()\n\n        if response == .alertFirstButtonReturn {\n            Self.openTerminalWithGcloudCommand()\n        }\n\n        // Refresh after user may have logged in\n        self.loginPhase = .idle\n        Task { @MainActor in\n            try? await Task.sleep(for: .seconds(2))\n            await self.store.refresh()\n        }\n    }\n\n    private static func openTerminalWithGcloudCommand() {\n        let script = \"\"\"\n        tell application \"Terminal\"\n            activate\n            do script \"gcloud auth application-default login --scopes=openid,https://www.googleapis.com/auth/userinfo.email,https://www.googleapis.com/auth/cloud-platform\"\n        end tell\n        \"\"\"\n\n        if let appleScript = NSAppleScript(source: script) {\n            var error: NSDictionary?\n            appleScript.executeAndReturnError(&error)\n            if let error {\n                CodexBarLog.logger(LogCategories.terminal).error(\n                    \"Failed to open Terminal\",\n                    metadata: [\"error\": String(describing: error)])\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/VertexAI/VertexAIProviderImplementation.swift",
    "content": "import CodexBarCore\nimport CodexBarMacroSupport\nimport Foundation\n\n@ProviderImplementationRegistration\nstruct VertexAIProviderImplementation: ProviderImplementation {\n    let id: UsageProvider = .vertexai\n    let supportsLoginFlow: Bool = true\n\n    @MainActor\n    func runLoginFlow(context: ProviderLoginContext) async -> Bool {\n        await context.controller.runVertexAILoginFlow()\n        return false\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Warp/WarpProviderImplementation.swift",
    "content": "import AppKit\nimport CodexBarCore\nimport CodexBarMacroSupport\nimport Foundation\n\n@ProviderImplementationRegistration\nstruct WarpProviderImplementation: ProviderImplementation {\n    let id: UsageProvider = .warp\n\n    @MainActor\n    func observeSettings(_ settings: SettingsStore) {\n        _ = settings.warpAPIToken\n    }\n\n    @MainActor\n    func settingsFields(context: ProviderSettingsContext) -> [ProviderSettingsFieldDescriptor] {\n        [\n            ProviderSettingsFieldDescriptor(\n                id: \"warp-api-token\",\n                title: \"API key\",\n                subtitle: \"Stored in ~/.codexbar/config.json. In Warp, open Settings > Platform > API Keys, \"\n                    + \"then create one.\",\n                kind: .secure,\n                placeholder: \"wk-...\",\n                binding: context.stringBinding(\\.warpAPIToken),\n                actions: [\n                    ProviderSettingsActionDescriptor(\n                        id: \"warp-open-api-keys\",\n                        title: \"Open Warp API Key Guide\",\n                        style: .link,\n                        isVisible: nil,\n                        perform: {\n                            if let url = URL(string: \"https://docs.warp.dev/reference/cli/api-keys\") {\n                                NSWorkspace.shared.open(url)\n                            }\n                        }),\n                ],\n                isVisible: nil,\n                onActivate: nil),\n        ]\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Warp/WarpSettingsStore.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nextension SettingsStore {\n    var warpAPIToken: String {\n        get { self.configSnapshot.providerConfig(for: .warp)?.sanitizedAPIKey ?? \"\" }\n        set {\n            self.updateProviderConfig(provider: .warp) { entry in\n                entry.apiKey = self.normalizedConfigValue(newValue)\n            }\n            self.logSecretUpdate(provider: .warp, field: \"apiKey\", value: newValue)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Zai/ZaiProviderImplementation.swift",
    "content": "import AppKit\nimport CodexBarCore\nimport CodexBarMacroSupport\nimport Foundation\nimport SwiftUI\n\n@ProviderImplementationRegistration\nstruct ZaiProviderImplementation: ProviderImplementation {\n    let id: UsageProvider = .zai\n\n    @MainActor\n    func presentation(context _: ProviderPresentationContext) -> ProviderPresentation {\n        ProviderPresentation { _ in \"api\" }\n    }\n\n    @MainActor\n    func observeSettings(_ settings: SettingsStore) {\n        _ = settings.zaiAPIToken\n        _ = settings.zaiAPIRegion\n    }\n\n    @MainActor\n    func settingsSnapshot(context: ProviderSettingsSnapshotContext) -> ProviderSettingsSnapshotContribution? {\n        _ = context\n        return .zai(context.settings.zaiSettingsSnapshot())\n    }\n\n    @MainActor\n    func isAvailable(context: ProviderAvailabilityContext) -> Bool {\n        if ZaiSettingsReader.apiToken(environment: context.environment) != nil {\n            return true\n        }\n        context.settings.ensureZaiAPITokenLoaded()\n        return !context.settings.zaiAPIToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty\n    }\n\n    @MainActor\n    func settingsPickers(context: ProviderSettingsContext) -> [ProviderSettingsPickerDescriptor] {\n        let binding = Binding(\n            get: { context.settings.zaiAPIRegion.rawValue },\n            set: { raw in\n                context.settings.zaiAPIRegion = ZaiAPIRegion(rawValue: raw) ?? .global\n            })\n        let options = ZaiAPIRegion.allCases.map {\n            ProviderSettingsPickerOption(id: $0.rawValue, title: $0.displayName)\n        }\n\n        return [\n            ProviderSettingsPickerDescriptor(\n                id: \"zai-api-region\",\n                title: \"API region\",\n                subtitle: \"Use BigModel for the China mainland endpoints (open.bigmodel.cn).\",\n                binding: binding,\n                options: options,\n                isVisible: nil,\n                onChange: nil),\n        ]\n    }\n\n    @MainActor\n    func settingsFields(context: ProviderSettingsContext) -> [ProviderSettingsFieldDescriptor] {\n        _ = context\n        return []\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/Providers/Zai/ZaiSettingsStore.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nextension SettingsStore {\n    var zaiAPIRegion: ZaiAPIRegion {\n        get {\n            let raw = self.configSnapshot.providerConfig(for: .zai)?.region\n            return ZaiAPIRegion(rawValue: raw ?? \"\") ?? .global\n        }\n        set {\n            self.updateProviderConfig(provider: .zai) { entry in\n                entry.region = newValue.rawValue\n            }\n        }\n    }\n\n    var zaiAPIToken: String {\n        get { self.configSnapshot.providerConfig(for: .zai)?.sanitizedAPIKey ?? \"\" }\n        set {\n            self.updateProviderConfig(provider: .zai) { entry in\n                entry.apiKey = self.normalizedConfigValue(newValue)\n            }\n            self.logSecretUpdate(provider: .zai, field: \"apiKey\", value: newValue)\n        }\n    }\n\n    func ensureZaiAPITokenLoaded() {}\n}\n\nextension SettingsStore {\n    func zaiSettingsSnapshot() -> ProviderSettingsSnapshot.ZaiProviderSettings {\n        ProviderSettingsSnapshot.ZaiProviderSettings(apiRegion: self.zaiAPIRegion)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/SessionQuotaNotifications.swift",
    "content": "import CodexBarCore\nimport Foundation\n@preconcurrency import UserNotifications\n\nenum SessionQuotaTransition: Equatable {\n    case none\n    case depleted\n    case restored\n}\n\nenum SessionQuotaNotificationLogic {\n    static let depletedThreshold: Double = 0.0001\n\n    static func isDepleted(_ remaining: Double?) -> Bool {\n        guard let remaining else { return false }\n        return remaining <= Self.depletedThreshold\n    }\n\n    static func transition(previousRemaining: Double?, currentRemaining: Double?) -> SessionQuotaTransition {\n        guard let currentRemaining else { return .none }\n        guard let previousRemaining else { return .none }\n\n        let wasDepleted = previousRemaining <= Self.depletedThreshold\n        let isDepleted = currentRemaining <= Self.depletedThreshold\n\n        if !wasDepleted, isDepleted { return .depleted }\n        if wasDepleted, !isDepleted { return .restored }\n        return .none\n    }\n}\n\n@MainActor\nprotocol SessionQuotaNotifying: AnyObject {\n    func post(transition: SessionQuotaTransition, provider: UsageProvider, badge: NSNumber?)\n}\n\n@MainActor\nfinal class SessionQuotaNotifier: SessionQuotaNotifying {\n    private let logger = CodexBarLog.logger(LogCategories.sessionQuotaNotifications)\n\n    init() {}\n\n    func post(transition: SessionQuotaTransition, provider: UsageProvider, badge: NSNumber? = nil) {\n        guard transition != .none else { return }\n\n        let providerName = ProviderDescriptorRegistry.descriptor(for: provider).metadata.displayName\n\n        let (title, body) = switch transition {\n        case .none:\n            (\"\", \"\")\n        case .depleted:\n            (\"\\(providerName) session depleted\", \"0% left. Will notify when it's available again.\")\n        case .restored:\n            (\"\\(providerName) session restored\", \"Session quota is available again.\")\n        }\n\n        let providerText = provider.rawValue\n        let transitionText = String(describing: transition)\n        let idPrefix = \"session-\\(providerText)-\\(transitionText)\"\n        self.logger.info(\"enqueuing\", metadata: [\"prefix\": idPrefix])\n        AppNotifications.shared.post(idPrefix: idPrefix, title: title, body: body, badge: badge)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/SettingsStore+Config.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nextension SettingsStore {\n    func providerConfig(for provider: UsageProvider) -> ProviderConfig? {\n        self.configSnapshot.providerConfig(for: provider)\n    }\n\n    var tokenAccountsByProvider: [UsageProvider: ProviderTokenAccountData] {\n        get {\n            Dictionary(uniqueKeysWithValues: self.configSnapshot.providers.compactMap { entry in\n                guard let accounts = entry.tokenAccounts else { return nil }\n                return (entry.id, accounts)\n            })\n        }\n        set {\n            self.updateProviderTokenAccounts(newValue)\n        }\n    }\n}\n\nextension SettingsStore {\n    func resolvedCookieSource(\n        provider: UsageProvider,\n        fallback: ProviderCookieSource) -> ProviderCookieSource\n    {\n        let source = self.configSnapshot.providerConfig(for: provider)?.cookieSource ?? fallback\n        guard self.debugDisableKeychainAccess == false else { return source == .off ? .off : .manual }\n        return source\n    }\n\n    func logProviderModeChange(provider: UsageProvider, field: String, value: String) {\n        CodexBarLog.logger(LogCategories.settings).info(\n            \"Provider mode updated\",\n            metadata: [\"provider\": provider.rawValue, \"field\": field, \"value\": value])\n    }\n\n    func logSecretUpdate(provider: UsageProvider, field: String, value: String) {\n        var metadata = LogMetadata.secretSummary(value)\n        metadata[\"provider\"] = provider.rawValue\n        metadata[\"field\"] = field\n        CodexBarLog.logger(LogCategories.settings).info(\n            \"Provider secret updated\",\n            metadata: metadata)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/SettingsStore+ConfigPersistence.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nprivate enum ConfigChangeOrigin {\n    case localUser\n    case externalSync\n    case reload\n}\n\nprivate struct ConfigChangeContext {\n    let origin: ConfigChangeOrigin\n    let reason: String\n\n    static func local(reason: String) -> Self {\n        Self(origin: .localUser, reason: reason)\n    }\n\n    static func external(reason: String) -> Self {\n        Self(origin: .externalSync, reason: reason)\n    }\n\n    static func reload(reason: String) -> Self {\n        Self(origin: .reload, reason: reason)\n    }\n\n    var shouldBroadcast: Bool {\n        switch self.origin {\n        case .localUser:\n            true\n        case .externalSync, .reload:\n            false\n        }\n    }\n}\n\nextension SettingsStore {\n    private func updateConfig(reason: String, mutate: (inout CodexBarConfig) -> Void) {\n        guard !self.configLoading else { return }\n        var config = self.config\n        mutate(&config)\n        self.config = config.normalized()\n        self.updateProviderState(config: self.config)\n        self.schedulePersistConfig()\n        self.bumpConfigRevision(.local(reason: reason))\n    }\n\n    func updateProviderConfig(provider: UsageProvider, mutate: (inout ProviderConfig) -> Void) {\n        self.updateConfig(reason: \"provider-\\(provider.rawValue)\") { config in\n            if let index = config.providers.firstIndex(where: { $0.id == provider }) {\n                var entry = config.providers[index]\n                mutate(&entry)\n                config.providers[index] = entry\n            } else {\n                var entry = ProviderConfig(id: provider)\n                mutate(&entry)\n                config.providers.append(entry)\n            }\n        }\n    }\n\n    func updateProviderTokenAccounts(_ accounts: [UsageProvider: ProviderTokenAccountData]) {\n        let summary = accounts\n            .sorted { $0.key.rawValue < $1.key.rawValue }\n            .map { \"\\($0.key.rawValue)=\\($0.value.accounts.count)\" }\n            .joined(separator: \",\")\n        CodexBarLog.logger(LogCategories.tokenAccounts).info(\n            \"Token accounts updated\",\n            metadata: [\n                \"providers\": \"\\(accounts.count)\",\n                \"summary\": summary,\n            ])\n        self.updateConfig(reason: \"token-accounts\") { config in\n            var seen: Set<UsageProvider> = []\n            for index in config.providers.indices {\n                let provider = config.providers[index].id\n                config.providers[index].tokenAccounts = accounts[provider]\n                seen.insert(provider)\n            }\n            for (provider, data) in accounts where !seen.contains(provider) {\n                config.providers.append(ProviderConfig(id: provider, tokenAccounts: data))\n            }\n        }\n    }\n\n    func setProviderOrder(_ order: [UsageProvider]) {\n        self.updateConfig(reason: \"order\") { config in\n            let configsByID = Dictionary(uniqueKeysWithValues: config.providers.map { ($0.id, $0) })\n            var seen: Set<UsageProvider> = []\n            var ordered: [ProviderConfig] = []\n            ordered.reserveCapacity(max(order.count, config.providers.count))\n\n            for provider in order {\n                guard !seen.contains(provider) else { continue }\n                seen.insert(provider)\n                ordered.append(configsByID[provider] ?? ProviderConfig(id: provider))\n            }\n\n            for provider in UsageProvider.allCases where !seen.contains(provider) {\n                ordered.append(configsByID[provider] ?? ProviderConfig(id: provider))\n            }\n\n            config.providers = ordered\n        }\n    }\n\n    func reloadConfig(reason: String) {\n        guard !self.configLoading else { return }\n        do {\n            guard let loaded = try self.configStore.load() else { return }\n            self.applyExternalConfig(loaded, reason: \"reload-\\(reason)\")\n        } catch {\n            CodexBarLog.logger(LogCategories.configStore).error(\"Failed to reload config: \\(error)\")\n        }\n    }\n\n    func applyExternalConfig(_ config: CodexBarConfig, reason: String) {\n        guard !self.configLoading else { return }\n        self.configLoading = true\n        self.config = config\n        self.updateProviderState(config: config)\n        self.configLoading = false\n        self.bumpConfigRevision(.external(reason: \"sync-\\(reason)\"))\n    }\n\n    private func bumpConfigRevision(_ context: ConfigChangeContext) {\n        self.configRevision &+= 1\n        CodexBarLog.logger(LogCategories.settings)\n            .debug(\"Config revision bumped (\\(context.reason)) -> \\(self.configRevision)\")\n        guard context.shouldBroadcast else { return }\n        NotificationCenter.default.post(\n            name: .codexbarProviderConfigDidChange,\n            object: self,\n            userInfo: [\n                \"config\": self.config,\n                \"reason\": context.reason,\n                \"revision\": self.configRevision,\n            ])\n    }\n\n    func normalizedConfigValue(_ raw: String) -> String? {\n        let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)\n        return trimmed.isEmpty ? nil : trimmed\n    }\n\n    func schedulePersistConfig() {\n        guard !self.configLoading else { return }\n        self.configPersistTask?.cancel()\n        if Self.isRunningTests {\n            do {\n                try self.configStore.save(self.config)\n            } catch {\n                CodexBarLog.logger(LogCategories.configStore).error(\"Failed to persist config: \\(error)\")\n            }\n            return\n        }\n        let store = self.configStore\n        self.configPersistTask = Task { @MainActor in\n            do {\n                try await Task.sleep(nanoseconds: 350_000_000)\n            } catch {\n                return\n            }\n            guard !Task.isCancelled else { return }\n            let snapshot = self.config\n            let error: (any Error)? = await Task.detached(priority: .utility) {\n                do {\n                    try store.save(snapshot)\n                    return nil\n                } catch {\n                    return error\n                }\n            }.value\n            if let error {\n                CodexBarLog.logger(LogCategories.configStore).error(\"Failed to persist config: \\(error)\")\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/SettingsStore+Defaults.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport ServiceManagement\n\nextension SettingsStore {\n    private static let mergedOverviewSelectionEditedActiveProvidersKey = \"mergedOverviewSelectionEditedActiveProviders\"\n\n    var refreshFrequency: RefreshFrequency {\n        get { self.defaultsState.refreshFrequency }\n        set {\n            self.defaultsState.refreshFrequency = newValue\n            self.userDefaults.set(newValue.rawValue, forKey: \"refreshFrequency\")\n        }\n    }\n\n    var launchAtLogin: Bool {\n        get { self.defaultsState.launchAtLogin }\n        set {\n            self.defaultsState.launchAtLogin = newValue\n            self.userDefaults.set(newValue, forKey: \"launchAtLogin\")\n            LaunchAtLoginManager.setEnabled(newValue)\n        }\n    }\n\n    var debugMenuEnabled: Bool {\n        get { self.defaultsState.debugMenuEnabled }\n        set {\n            self.defaultsState.debugMenuEnabled = newValue\n            self.userDefaults.set(newValue, forKey: \"debugMenuEnabled\")\n        }\n    }\n\n    var debugDisableKeychainAccess: Bool {\n        get { self.defaultsState.debugDisableKeychainAccess }\n        set {\n            self.defaultsState.debugDisableKeychainAccess = newValue\n            self.userDefaults.set(newValue, forKey: \"debugDisableKeychainAccess\")\n            Self.sharedDefaults?.set(newValue, forKey: \"debugDisableKeychainAccess\")\n            KeychainAccessGate.isDisabled = newValue\n        }\n    }\n\n    var debugFileLoggingEnabled: Bool {\n        get { self.defaultsState.debugFileLoggingEnabled }\n        set {\n            self.defaultsState.debugFileLoggingEnabled = newValue\n            self.userDefaults.set(newValue, forKey: \"debugFileLoggingEnabled\")\n            CodexBarLog.setFileLoggingEnabled(newValue)\n        }\n    }\n\n    var debugLogLevel: CodexBarLog.Level {\n        get {\n            let raw = self.defaultsState.debugLogLevelRaw\n            return CodexBarLog.parseLevel(raw) ?? .verbose\n        }\n        set {\n            self.defaultsState.debugLogLevelRaw = newValue.rawValue\n            self.userDefaults.set(newValue.rawValue, forKey: \"debugLogLevel\")\n            CodexBarLog.setLogLevel(newValue)\n        }\n    }\n\n    var debugKeepCLISessionsAlive: Bool {\n        get { self.defaultsState.debugKeepCLISessionsAlive }\n        set {\n            self.defaultsState.debugKeepCLISessionsAlive = newValue\n            self.userDefaults.set(newValue, forKey: \"debugKeepCLISessionsAlive\")\n        }\n    }\n\n    var isVerboseLoggingEnabled: Bool {\n        self.debugLogLevel.rank <= CodexBarLog.Level.verbose.rank\n    }\n\n    private var debugLoadingPatternRaw: String? {\n        get { self.defaultsState.debugLoadingPatternRaw }\n        set {\n            self.defaultsState.debugLoadingPatternRaw = newValue\n            if let raw = newValue {\n                self.userDefaults.set(raw, forKey: \"debugLoadingPattern\")\n            } else {\n                self.userDefaults.removeObject(forKey: \"debugLoadingPattern\")\n            }\n        }\n    }\n\n    var statusChecksEnabled: Bool {\n        get { self.defaultsState.statusChecksEnabled }\n        set {\n            self.defaultsState.statusChecksEnabled = newValue\n            self.userDefaults.set(newValue, forKey: \"statusChecksEnabled\")\n        }\n    }\n\n    var sessionQuotaNotificationsEnabled: Bool {\n        get { self.defaultsState.sessionQuotaNotificationsEnabled }\n        set {\n            self.defaultsState.sessionQuotaNotificationsEnabled = newValue\n            self.userDefaults.set(newValue, forKey: \"sessionQuotaNotificationsEnabled\")\n        }\n    }\n\n    var usageBarsShowUsed: Bool {\n        get { self.defaultsState.usageBarsShowUsed }\n        set {\n            self.defaultsState.usageBarsShowUsed = newValue\n            self.userDefaults.set(newValue, forKey: \"usageBarsShowUsed\")\n        }\n    }\n\n    var resetTimesShowAbsolute: Bool {\n        get { self.defaultsState.resetTimesShowAbsolute }\n        set {\n            self.defaultsState.resetTimesShowAbsolute = newValue\n            self.userDefaults.set(newValue, forKey: \"resetTimesShowAbsolute\")\n        }\n    }\n\n    var menuBarShowsBrandIconWithPercent: Bool {\n        get { self.defaultsState.menuBarShowsBrandIconWithPercent }\n        set {\n            self.defaultsState.menuBarShowsBrandIconWithPercent = newValue\n            self.userDefaults.set(newValue, forKey: \"menuBarShowsBrandIconWithPercent\")\n        }\n    }\n\n    private var menuBarDisplayModeRaw: String? {\n        get { self.defaultsState.menuBarDisplayModeRaw }\n        set {\n            self.defaultsState.menuBarDisplayModeRaw = newValue\n            if let raw = newValue {\n                self.userDefaults.set(raw, forKey: \"menuBarDisplayMode\")\n            } else {\n                self.userDefaults.removeObject(forKey: \"menuBarDisplayMode\")\n            }\n        }\n    }\n\n    var menuBarDisplayMode: MenuBarDisplayMode {\n        get { MenuBarDisplayMode(rawValue: self.menuBarDisplayModeRaw ?? \"\") ?? .percent }\n        set { self.menuBarDisplayModeRaw = newValue.rawValue }\n    }\n\n    var showAllTokenAccountsInMenu: Bool {\n        get { self.defaultsState.showAllTokenAccountsInMenu }\n        set {\n            self.defaultsState.showAllTokenAccountsInMenu = newValue\n            self.userDefaults.set(newValue, forKey: \"showAllTokenAccountsInMenu\")\n        }\n    }\n\n    var historicalTrackingEnabled: Bool {\n        get { self.defaultsState.historicalTrackingEnabled }\n        set {\n            self.defaultsState.historicalTrackingEnabled = newValue\n            self.userDefaults.set(newValue, forKey: \"historicalTrackingEnabled\")\n        }\n    }\n\n    var menuBarMetricPreferencesRaw: [String: String] {\n        get { self.defaultsState.menuBarMetricPreferencesRaw }\n        set {\n            self.defaultsState.menuBarMetricPreferencesRaw = newValue\n            self.userDefaults.set(newValue, forKey: \"menuBarMetricPreferences\")\n        }\n    }\n\n    var costUsageEnabled: Bool {\n        get { self.defaultsState.costUsageEnabled }\n        set {\n            self.defaultsState.costUsageEnabled = newValue\n            self.userDefaults.set(newValue, forKey: \"tokenCostUsageEnabled\")\n        }\n    }\n\n    var hidePersonalInfo: Bool {\n        get { self.defaultsState.hidePersonalInfo }\n        set {\n            self.defaultsState.hidePersonalInfo = newValue\n            self.userDefaults.set(newValue, forKey: \"hidePersonalInfo\")\n        }\n    }\n\n    var randomBlinkEnabled: Bool {\n        get { self.defaultsState.randomBlinkEnabled }\n        set {\n            self.defaultsState.randomBlinkEnabled = newValue\n            self.userDefaults.set(newValue, forKey: \"randomBlinkEnabled\")\n        }\n    }\n\n    var menuBarShowsHighestUsage: Bool {\n        get { self.defaultsState.menuBarShowsHighestUsage }\n        set {\n            self.defaultsState.menuBarShowsHighestUsage = newValue\n            self.userDefaults.set(newValue, forKey: \"menuBarShowsHighestUsage\")\n        }\n    }\n\n    var claudeOAuthKeychainPromptMode: ClaudeOAuthKeychainPromptMode {\n        get {\n            let raw = self.defaultsState.claudeOAuthKeychainPromptModeRaw\n            return ClaudeOAuthKeychainPromptMode(rawValue: raw ?? \"\") ?? .onlyOnUserAction\n        }\n        set {\n            self.defaultsState.claudeOAuthKeychainPromptModeRaw = newValue.rawValue\n            self.userDefaults.set(newValue.rawValue, forKey: \"claudeOAuthKeychainPromptMode\")\n        }\n    }\n\n    var claudeOAuthKeychainReadStrategy: ClaudeOAuthKeychainReadStrategy {\n        get {\n            let raw = self.defaultsState.claudeOAuthKeychainReadStrategyRaw\n            return ClaudeOAuthKeychainReadStrategy(rawValue: raw ?? \"\") ?? .securityFramework\n        }\n        set {\n            self.defaultsState.claudeOAuthKeychainReadStrategyRaw = newValue.rawValue\n            self.userDefaults.set(newValue.rawValue, forKey: \"claudeOAuthKeychainReadStrategy\")\n        }\n    }\n\n    var claudeOAuthPromptFreeCredentialsEnabled: Bool {\n        get { self.claudeOAuthKeychainReadStrategy == .securityCLIExperimental }\n        set {\n            self.claudeOAuthKeychainReadStrategy = newValue\n                ? .securityCLIExperimental\n                : .securityFramework\n        }\n    }\n\n    var claudeWebExtrasEnabled: Bool {\n        get { self.claudeWebExtrasEnabledRaw }\n        set { self.claudeWebExtrasEnabledRaw = newValue }\n    }\n\n    private var claudeWebExtrasEnabledRaw: Bool {\n        get { self.defaultsState.claudeWebExtrasEnabledRaw }\n        set {\n            self.defaultsState.claudeWebExtrasEnabledRaw = newValue\n            self.userDefaults.set(newValue, forKey: \"claudeWebExtrasEnabled\")\n            CodexBarLog.logger(LogCategories.settings).info(\n                \"Claude web extras updated\",\n                metadata: [\"enabled\": newValue ? \"1\" : \"0\"])\n        }\n    }\n\n    var showOptionalCreditsAndExtraUsage: Bool {\n        get { self.defaultsState.showOptionalCreditsAndExtraUsage }\n        set {\n            self.defaultsState.showOptionalCreditsAndExtraUsage = newValue\n            self.userDefaults.set(newValue, forKey: \"showOptionalCreditsAndExtraUsage\")\n        }\n    }\n\n    var openAIWebAccessEnabled: Bool {\n        get { self.defaultsState.openAIWebAccessEnabled }\n        set {\n            self.defaultsState.openAIWebAccessEnabled = newValue\n            self.userDefaults.set(newValue, forKey: \"openAIWebAccessEnabled\")\n            CodexBarLog.logger(LogCategories.settings).info(\n                \"OpenAI web access updated\",\n                metadata: [\"enabled\": newValue ? \"1\" : \"0\"])\n        }\n    }\n\n    var jetbrainsIDEBasePath: String {\n        get { self.defaultsState.jetbrainsIDEBasePath }\n        set {\n            self.defaultsState.jetbrainsIDEBasePath = newValue\n            self.userDefaults.set(newValue, forKey: \"jetbrainsIDEBasePath\")\n        }\n    }\n\n    var mergeIcons: Bool {\n        get { self.defaultsState.mergeIcons }\n        set {\n            self.defaultsState.mergeIcons = newValue\n            self.userDefaults.set(newValue, forKey: \"mergeIcons\")\n        }\n    }\n\n    var switcherShowsIcons: Bool {\n        get { self.defaultsState.switcherShowsIcons }\n        set {\n            self.defaultsState.switcherShowsIcons = newValue\n            self.userDefaults.set(newValue, forKey: \"switcherShowsIcons\")\n        }\n    }\n\n    var mergedMenuLastSelectedWasOverview: Bool {\n        get { self.defaultsState.mergedMenuLastSelectedWasOverview }\n        set {\n            self.defaultsState.mergedMenuLastSelectedWasOverview = newValue\n            self.userDefaults.set(newValue, forKey: \"mergedMenuLastSelectedWasOverview\")\n        }\n    }\n\n    private var mergedOverviewSelectedProvidersRaw: [String] {\n        get { self.defaultsState.mergedOverviewSelectedProvidersRaw }\n        set {\n            self.defaultsState.mergedOverviewSelectedProvidersRaw = newValue\n            self.userDefaults.set(newValue, forKey: \"mergedOverviewSelectedProviders\")\n        }\n    }\n\n    private var selectedMenuProviderRaw: String? {\n        get { self.defaultsState.selectedMenuProviderRaw }\n        set {\n            self.defaultsState.selectedMenuProviderRaw = newValue\n            if let raw = newValue {\n                self.userDefaults.set(raw, forKey: \"selectedMenuProvider\")\n            } else {\n                self.userDefaults.removeObject(forKey: \"selectedMenuProvider\")\n            }\n        }\n    }\n\n    var selectedMenuProvider: UsageProvider? {\n        get { self.selectedMenuProviderRaw.flatMap(UsageProvider.init(rawValue:)) }\n        set {\n            self.selectedMenuProviderRaw = newValue?.rawValue\n        }\n    }\n\n    var mergedOverviewSelectedProviders: [UsageProvider] {\n        get {\n            Self.decodeProviders(\n                self.mergedOverviewSelectedProvidersRaw,\n                maxCount: Self.mergedOverviewProviderLimit)\n        }\n        set {\n            let normalized = Self.normalizeProviders(newValue, maxCount: Self.mergedOverviewProviderLimit)\n            self.mergedOverviewSelectedProvidersRaw = normalized.map(\\.rawValue)\n        }\n    }\n\n    private var hasMergedOverviewSelectionPreference: Bool {\n        self.userDefaults.object(forKey: \"mergedOverviewSelectedProviders\") != nil\n    }\n\n    private var mergedOverviewSelectionEditedActiveProvidersRaw: [String]? {\n        get {\n            self.userDefaults.array(forKey: Self.mergedOverviewSelectionEditedActiveProvidersKey) as? [String]\n        }\n        set {\n            if let newValue {\n                self.userDefaults.set(newValue, forKey: Self.mergedOverviewSelectionEditedActiveProvidersKey)\n            } else {\n                self.userDefaults.removeObject(forKey: Self.mergedOverviewSelectionEditedActiveProvidersKey)\n            }\n        }\n    }\n\n    private func mergedOverviewSelectionApplies(to activeProviders: [UsageProvider]) -> Bool {\n        guard let editedRaw = self.mergedOverviewSelectionEditedActiveProvidersRaw else { return false }\n        let editedSet = Set(editedRaw)\n        let activeSet = Set(Self.normalizeProviders(activeProviders).map(\\.rawValue))\n        return editedSet == activeSet\n    }\n\n    private func markMergedOverviewSelectionEdited(for activeProviders: [UsageProvider]) {\n        let signature = Set(Self.normalizeProviders(activeProviders).map(\\.rawValue))\n        self.mergedOverviewSelectionEditedActiveProvidersRaw = Array(signature).sorted()\n    }\n\n    private func clearMergedOverviewSelectionPreference() {\n        self.defaultsState.mergedOverviewSelectedProvidersRaw = []\n        self.userDefaults.removeObject(forKey: \"mergedOverviewSelectedProviders\")\n        self.mergedOverviewSelectionEditedActiveProvidersRaw = nil\n    }\n\n    func resolvedMergedOverviewProviders(\n        activeProviders: [UsageProvider],\n        maxVisibleProviders: Int = SettingsStore.mergedOverviewProviderLimit) -> [UsageProvider]\n    {\n        guard maxVisibleProviders > 0 else { return [] }\n        let normalizedActive = Self.normalizeProviders(activeProviders)\n        guard self.hasMergedOverviewSelectionPreference else {\n            return Array(normalizedActive.prefix(maxVisibleProviders))\n        }\n        if normalizedActive.count <= maxVisibleProviders,\n           !self.mergedOverviewSelectionApplies(to: normalizedActive)\n        {\n            return normalizedActive\n        }\n\n        let selectedSet = Set(self.mergedOverviewSelectedProviders)\n        return Array(normalizedActive.filter { selectedSet.contains($0) }.prefix(maxVisibleProviders))\n    }\n\n    @discardableResult\n    func reconcileMergedOverviewSelectedProviders(\n        activeProviders: [UsageProvider],\n        maxVisibleProviders: Int = SettingsStore.mergedOverviewProviderLimit) -> [UsageProvider]\n    {\n        guard maxVisibleProviders > 0 else {\n            self.clearMergedOverviewSelectionPreference()\n            return []\n        }\n\n        let normalizedActive = Self.normalizeProviders(activeProviders)\n        if normalizedActive.isEmpty {\n            self.clearMergedOverviewSelectionPreference()\n            return []\n        }\n\n        let shouldPersistResolvedSelection = normalizedActive.count > maxVisibleProviders ||\n            self.mergedOverviewSelectionApplies(to: normalizedActive)\n\n        if self.hasMergedOverviewSelectionPreference, shouldPersistResolvedSelection {\n            let selectedSet = Set(self.mergedOverviewSelectedProviders)\n            let sanitizedSelection = Array(\n                normalizedActive\n                    .filter { selectedSet.contains($0) }\n                    .prefix(maxVisibleProviders))\n            if sanitizedSelection != self.mergedOverviewSelectedProviders {\n                self.mergedOverviewSelectedProviders = sanitizedSelection\n            }\n        }\n\n        return self.resolvedMergedOverviewProviders(\n            activeProviders: normalizedActive,\n            maxVisibleProviders: maxVisibleProviders)\n    }\n\n    @discardableResult\n    func setMergedOverviewProviderSelection(\n        provider: UsageProvider,\n        isSelected: Bool,\n        activeProviders: [UsageProvider],\n        maxVisibleProviders: Int = SettingsStore.mergedOverviewProviderLimit) -> [UsageProvider]\n    {\n        guard maxVisibleProviders > 0 else {\n            self.clearMergedOverviewSelectionPreference()\n            return []\n        }\n\n        let normalizedActive = Self.normalizeProviders(activeProviders)\n        guard normalizedActive.contains(provider) else {\n            return self.resolvedMergedOverviewProviders(\n                activeProviders: normalizedActive,\n                maxVisibleProviders: maxVisibleProviders)\n        }\n\n        let currentSelection = self.resolvedMergedOverviewProviders(\n            activeProviders: normalizedActive,\n            maxVisibleProviders: maxVisibleProviders)\n        var updatedSet = Set(currentSelection)\n\n        if isSelected {\n            guard updatedSet.contains(provider) || currentSelection.count < maxVisibleProviders else {\n                return currentSelection\n            }\n            updatedSet.insert(provider)\n        } else {\n            updatedSet.remove(provider)\n        }\n\n        let updatedSelection = Array(\n            normalizedActive\n                .filter { updatedSet.contains($0) }\n                .prefix(maxVisibleProviders))\n        self.mergedOverviewSelectedProviders = updatedSelection\n        self.markMergedOverviewSelectionEdited(for: normalizedActive)\n        return updatedSelection\n    }\n\n    var providerDetectionCompleted: Bool {\n        get { self.defaultsState.providerDetectionCompleted }\n        set {\n            self.defaultsState.providerDetectionCompleted = newValue\n            self.userDefaults.set(newValue, forKey: \"providerDetectionCompleted\")\n        }\n    }\n\n    var debugLoadingPattern: LoadingPattern? {\n        get { self.debugLoadingPatternRaw.flatMap(LoadingPattern.init(rawValue:)) }\n        set { self.debugLoadingPatternRaw = newValue?.rawValue }\n    }\n}\n\nextension SettingsStore {\n    private static func normalizeProviders(_ providers: [UsageProvider], maxCount: Int? = nil) -> [UsageProvider] {\n        var seen: Set<UsageProvider> = []\n        var normalized: [UsageProvider] = []\n        for provider in providers where !seen.contains(provider) {\n            seen.insert(provider)\n            normalized.append(provider)\n            if let maxCount, normalized.count >= maxCount { break }\n        }\n        return normalized\n    }\n\n    private static func decodeProviders(_ rawProviders: [String], maxCount: Int? = nil) -> [UsageProvider] {\n        var providers: [UsageProvider] = []\n        providers.reserveCapacity(rawProviders.count)\n        for raw in rawProviders {\n            guard let provider = UsageProvider(rawValue: raw) else { continue }\n            providers.append(provider)\n        }\n        return self.normalizeProviders(providers, maxCount: maxCount)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/SettingsStore+MenuObservation.swift",
    "content": "import Foundation\n\nextension SettingsStore {\n    var menuObservationToken: Int {\n        _ = self.providerOrder\n        _ = self.providerEnablement\n        _ = self.refreshFrequency\n        _ = self.launchAtLogin\n        _ = self.debugMenuEnabled\n        _ = self.debugDisableKeychainAccess\n        _ = self.debugKeepCLISessionsAlive\n        _ = self.statusChecksEnabled\n        _ = self.sessionQuotaNotificationsEnabled\n        _ = self.usageBarsShowUsed\n        _ = self.resetTimesShowAbsolute\n        _ = self.menuBarShowsBrandIconWithPercent\n        _ = self.menuBarShowsHighestUsage\n        _ = self.menuBarDisplayMode\n        _ = self.historicalTrackingEnabled\n        _ = self.showAllTokenAccountsInMenu\n        _ = self.menuBarMetricPreferencesRaw\n        _ = self.costUsageEnabled\n        _ = self.hidePersonalInfo\n        _ = self.randomBlinkEnabled\n        _ = self.claudeOAuthKeychainPromptMode\n        _ = self.claudeOAuthKeychainReadStrategy\n        _ = self.claudeWebExtrasEnabled\n        _ = self.showOptionalCreditsAndExtraUsage\n        _ = self.openAIWebAccessEnabled\n        _ = self.codexUsageDataSource\n        _ = self.claudeUsageDataSource\n        _ = self.kiloUsageDataSource\n        _ = self.kiloExtrasEnabled\n        _ = self.codexCookieSource\n        _ = self.claudeCookieSource\n        _ = self.cursorCookieSource\n        _ = self.opencodeCookieSource\n        _ = self.factoryCookieSource\n        _ = self.minimaxCookieSource\n        _ = self.minimaxAPIRegion\n        _ = self.kimiCookieSource\n        _ = self.augmentCookieSource\n        _ = self.ampCookieSource\n        _ = self.ollamaCookieSource\n        _ = self.mergeIcons\n        _ = self.switcherShowsIcons\n        _ = self.mergedMenuLastSelectedWasOverview\n        _ = self.mergedOverviewSelectedProviders\n        _ = self.zaiAPIToken\n        _ = self.syntheticAPIToken\n        _ = self.codexCookieHeader\n        _ = self.claudeCookieHeader\n        _ = self.cursorCookieHeader\n        _ = self.opencodeCookieHeader\n        _ = self.opencodeWorkspaceID\n        _ = self.factoryCookieHeader\n        _ = self.minimaxCookieHeader\n        _ = self.minimaxAPIToken\n        _ = self.kimiManualCookieHeader\n        _ = self.kimiK2APIToken\n        _ = self.kiloAPIToken\n        _ = self.augmentCookieHeader\n        _ = self.ampCookieHeader\n        _ = self.ollamaCookieHeader\n        _ = self.copilotAPIToken\n        _ = self.warpAPIToken\n        _ = self.tokenAccountsByProvider\n        _ = self.debugLoadingPattern\n        _ = self.selectedMenuProvider\n        _ = self.configRevision\n        return 0\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/SettingsStore+MenuPreferences.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nextension SettingsStore {\n    func menuBarMetricPreference(for provider: UsageProvider) -> MenuBarMetricPreference {\n        if provider == .zai { return .primary }\n        if provider == .openrouter {\n            let raw = self.menuBarMetricPreferencesRaw[provider.rawValue] ?? \"\"\n            let preference = MenuBarMetricPreference(rawValue: raw) ?? .automatic\n            switch preference {\n            case .automatic, .primary:\n                return preference\n            case .secondary, .average:\n                return .automatic\n            }\n        }\n        let raw = self.menuBarMetricPreferencesRaw[provider.rawValue] ?? \"\"\n        let preference = MenuBarMetricPreference(rawValue: raw) ?? .automatic\n        if preference == .average, !self.menuBarMetricSupportsAverage(for: provider) {\n            return .automatic\n        }\n        return preference\n    }\n\n    func setMenuBarMetricPreference(_ preference: MenuBarMetricPreference, for provider: UsageProvider) {\n        if provider == .zai {\n            self.menuBarMetricPreferencesRaw[provider.rawValue] = MenuBarMetricPreference.primary.rawValue\n            return\n        }\n        if provider == .openrouter {\n            switch preference {\n            case .automatic, .primary:\n                self.menuBarMetricPreferencesRaw[provider.rawValue] = preference.rawValue\n            case .secondary, .average:\n                self.menuBarMetricPreferencesRaw[provider.rawValue] = MenuBarMetricPreference.automatic.rawValue\n            }\n            return\n        }\n        self.menuBarMetricPreferencesRaw[provider.rawValue] = preference.rawValue\n    }\n\n    func menuBarMetricSupportsAverage(for provider: UsageProvider) -> Bool {\n        provider == .gemini\n    }\n\n    func isCostUsageEffectivelyEnabled(for provider: UsageProvider) -> Bool {\n        self.costUsageEnabled\n            && ProviderDescriptorRegistry.descriptor(for: provider).tokenCost.supportsTokenCost\n    }\n\n    var resetTimeDisplayStyle: ResetTimeDisplayStyle {\n        self.resetTimesShowAbsolute ? .absolute : .countdown\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/SettingsStore+ProviderDetection.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nextension SettingsStore {\n    func runInitialProviderDetectionIfNeeded(force: Bool = false) {\n        guard force || !self.providerDetectionCompleted else { return }\n        LoginShellPathCache.shared.captureOnce { [weak self] _ in\n            Task { @MainActor in\n                await self?.applyProviderDetection()\n            }\n        }\n    }\n\n    func applyProviderDetection() async {\n        guard !self.providerDetectionCompleted else { return }\n        let codexInstalled = BinaryLocator.resolveCodexBinary() != nil\n        let claudeInstalled = BinaryLocator.resolveClaudeBinary() != nil\n        let geminiInstalled = BinaryLocator.resolveGeminiBinary() != nil\n        let antigravityRunning = await AntigravityStatusProbe.isRunning()\n        let logger = CodexBarLog.logger(LogCategories.providerDetection)\n\n        // If none installed, keep Codex enabled to match previous behavior.\n        let noneInstalled = !codexInstalled && !claudeInstalled && !geminiInstalled && !antigravityRunning\n        let enableCodex = codexInstalled || noneInstalled\n        let enableClaude = claudeInstalled\n        let enableGemini = geminiInstalled\n        let enableAntigravity = antigravityRunning\n\n        logger.info(\n            \"Provider detection results\",\n            metadata: [\n                \"codexInstalled\": codexInstalled ? \"1\" : \"0\",\n                \"claudeInstalled\": claudeInstalled ? \"1\" : \"0\",\n                \"geminiInstalled\": geminiInstalled ? \"1\" : \"0\",\n                \"antigravityRunning\": antigravityRunning ? \"1\" : \"0\",\n            ])\n        logger.info(\n            \"Provider detection enablement\",\n            metadata: [\n                \"codex\": enableCodex ? \"1\" : \"0\",\n                \"claude\": enableClaude ? \"1\" : \"0\",\n                \"gemini\": enableGemini ? \"1\" : \"0\",\n                \"antigravity\": enableAntigravity ? \"1\" : \"0\",\n            ])\n\n        self.updateProviderConfig(provider: .codex) { entry in\n            entry.enabled = enableCodex\n        }\n        self.updateProviderConfig(provider: .claude) { entry in\n            entry.enabled = enableClaude\n        }\n        self.updateProviderConfig(provider: .gemini) { entry in\n            entry.enabled = enableGemini\n        }\n        self.updateProviderConfig(provider: .antigravity) { entry in\n            entry.enabled = enableAntigravity\n        }\n        self.providerDetectionCompleted = true\n        logger.info(\"Provider detection completed\")\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/SettingsStore+TokenAccounts.swift",
    "content": "import AppKit\nimport CodexBarCore\nimport Foundation\n\nextension SettingsStore {\n    func tokenAccountsData(for provider: UsageProvider) -> ProviderTokenAccountData? {\n        guard TokenAccountSupportCatalog.support(for: provider) != nil else { return nil }\n        return self.configSnapshot.providerConfig(for: provider)?.tokenAccounts\n    }\n\n    func tokenAccounts(for provider: UsageProvider) -> [ProviderTokenAccount] {\n        self.tokenAccountsData(for: provider)?.accounts ?? []\n    }\n\n    func selectedTokenAccount(for provider: UsageProvider) -> ProviderTokenAccount? {\n        guard let data = self.tokenAccountsData(for: provider), !data.accounts.isEmpty else { return nil }\n        let index = data.clampedActiveIndex()\n        return data.accounts[index]\n    }\n\n    func setActiveTokenAccountIndex(_ index: Int, for provider: UsageProvider) {\n        guard let data = self.tokenAccountsData(for: provider), !data.accounts.isEmpty else { return }\n        let clamped = min(max(index, 0), data.accounts.count - 1)\n        let updated = ProviderTokenAccountData(\n            version: data.version,\n            accounts: data.accounts,\n            activeIndex: clamped)\n        self.updateProviderConfig(provider: provider) { entry in\n            entry.tokenAccounts = updated\n        }\n        CodexBarLog.logger(LogCategories.tokenAccounts).info(\n            \"Active token account updated\",\n            metadata: [\n                \"provider\": provider.rawValue,\n                \"index\": \"\\(clamped)\",\n            ])\n    }\n\n    func addTokenAccount(provider: UsageProvider, label: String, token: String) {\n        guard TokenAccountSupportCatalog.support(for: provider) != nil else { return }\n        let trimmedToken = token.trimmingCharacters(in: .whitespacesAndNewlines)\n        guard !trimmedToken.isEmpty else { return }\n        let trimmedLabel = label.trimmingCharacters(in: .whitespacesAndNewlines)\n        let existing = self.tokenAccountsData(for: provider)\n        let accounts = existing?.accounts ?? []\n        let fallbackLabel = trimmedLabel.isEmpty ? \"Account \\(accounts.count + 1)\" : trimmedLabel\n        let account = ProviderTokenAccount(\n            id: UUID(),\n            label: fallbackLabel,\n            token: trimmedToken,\n            addedAt: Date().timeIntervalSince1970,\n            lastUsed: nil)\n        let updated = ProviderTokenAccountData(\n            version: existing?.version ?? 1,\n            accounts: accounts + [account],\n            activeIndex: accounts.count)\n        self.updateProviderConfig(provider: provider) { entry in\n            entry.tokenAccounts = updated\n        }\n        self.applyTokenAccountCookieSourceIfNeeded(provider: provider)\n        CodexBarLog.logger(LogCategories.tokenAccounts).info(\n            \"Token account added\",\n            metadata: [\n                \"provider\": provider.rawValue,\n                \"count\": \"\\(updated.accounts.count)\",\n            ])\n    }\n\n    func removeTokenAccount(provider: UsageProvider, accountID: UUID) {\n        guard let data = self.tokenAccountsData(for: provider), !data.accounts.isEmpty else { return }\n        let filtered = data.accounts.filter { $0.id != accountID }\n        self.updateProviderConfig(provider: provider) { entry in\n            if filtered.isEmpty {\n                entry.tokenAccounts = nil\n            } else {\n                let clamped = min(max(data.activeIndex, 0), filtered.count - 1)\n                entry.tokenAccounts = ProviderTokenAccountData(\n                    version: data.version,\n                    accounts: filtered,\n                    activeIndex: clamped)\n            }\n        }\n        CodexBarLog.logger(LogCategories.tokenAccounts).info(\n            \"Token account removed\",\n            metadata: [\n                \"provider\": provider.rawValue,\n                \"count\": \"\\(filtered.count)\",\n            ])\n    }\n\n    func ensureTokenAccountsLoaded() {\n        if self.tokenAccountsLoaded { return }\n        self.tokenAccountsLoaded = true\n    }\n\n    func reloadTokenAccounts() {\n        let log = CodexBarLog.logger(LogCategories.tokenAccounts)\n        let accounts: [UsageProvider: ProviderTokenAccountData]\n        do {\n            guard let loaded = try self.configStore.load() else { return }\n            accounts = Dictionary(uniqueKeysWithValues: loaded.providers.compactMap { entry in\n                guard let data = entry.tokenAccounts else { return nil }\n                return (entry.id, data)\n            })\n        } catch {\n            log.error(\"Failed to reload token accounts: \\(error)\")\n            return\n        }\n        self.tokenAccountsLoaded = true\n        self.updateProviderTokenAccounts(accounts)\n    }\n\n    func openTokenAccountsFile() {\n        do {\n            try self.configStore.save(self.config)\n        } catch {\n            CodexBarLog.logger(LogCategories.tokenAccounts).error(\"Failed to persist config: \\(error)\")\n            return\n        }\n        NSWorkspace.shared.open(self.configStore.fileURL)\n    }\n\n    private func applyTokenAccountCookieSourceIfNeeded(provider: UsageProvider) {\n        guard let support = TokenAccountSupportCatalog.support(for: provider),\n              support.requiresManualCookieSource\n        else { return }\n        ProviderCatalog.implementation(for: provider)?.applyTokenAccountCookieSource(settings: self)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/SettingsStore+TokenCost.swift",
    "content": "import Foundation\n\nextension SettingsStore {\n    func applyTokenCostDefaultIfNeeded() {\n        // Settings are persisted in UserDefaults.standard.\n        guard UserDefaults.standard.object(forKey: \"tokenCostUsageEnabled\") == nil else { return }\n\n        Task { @MainActor [weak self] in\n            guard let self else { return }\n            let hasSources = await Task.detached(priority: .utility) {\n                Self.hasAnyTokenCostUsageSources()\n            }.value\n            guard hasSources else { return }\n            guard UserDefaults.standard.object(forKey: \"tokenCostUsageEnabled\") == nil else { return }\n            self.costUsageEnabled = true\n        }\n    }\n\n    nonisolated static func hasAnyTokenCostUsageSources(\n        env: [String: String] = ProcessInfo.processInfo.environment,\n        fileManager: FileManager = .default) -> Bool\n    {\n        func hasAnyJsonl(in root: URL) -> Bool {\n            guard fileManager.fileExists(atPath: root.path) else { return false }\n            guard let enumerator = fileManager.enumerator(\n                at: root,\n                includingPropertiesForKeys: [.isRegularFileKey],\n                options: [.skipsHiddenFiles, .skipsPackageDescendants])\n            else { return false }\n\n            for case let url as URL in enumerator where url.pathExtension.lowercased() == \"jsonl\" {\n                return true\n            }\n            return false\n        }\n\n        let codexRoot: URL = {\n            let raw = env[\"CODEX_HOME\"]?.trimmingCharacters(in: .whitespacesAndNewlines)\n            if let raw, !raw.isEmpty {\n                return URL(fileURLWithPath: raw).appendingPathComponent(\"sessions\", isDirectory: true)\n            }\n            return fileManager.homeDirectoryForCurrentUser\n                .appendingPathComponent(\".codex\", isDirectory: true)\n                .appendingPathComponent(\"sessions\", isDirectory: true)\n        }()\n\n        let archivedCodexRoot: URL? = {\n            guard codexRoot.lastPathComponent == \"sessions\" else { return nil }\n            return codexRoot\n                .deletingLastPathComponent()\n                .appendingPathComponent(\"archived_sessions\", isDirectory: true)\n        }()\n\n        if hasAnyJsonl(in: codexRoot) { return true }\n        if let archivedCodexRoot, hasAnyJsonl(in: archivedCodexRoot) { return true }\n\n        let claudeRoots: [URL] = {\n            if let env = env[\"CLAUDE_CONFIG_DIR\"]?.trimmingCharacters(in: .whitespacesAndNewlines),\n               !env.isEmpty\n            {\n                return env.split(separator: \",\").map { part in\n                    let raw = String(part).trimmingCharacters(in: .whitespacesAndNewlines)\n                    let url = URL(fileURLWithPath: raw)\n                    if url.lastPathComponent == \"projects\" {\n                        return url\n                    }\n                    return url.appendingPathComponent(\"projects\", isDirectory: true)\n                }\n            }\n\n            let home = fileManager.homeDirectoryForCurrentUser\n            return [\n                home.appendingPathComponent(\".config/claude/projects\", isDirectory: true),\n                home.appendingPathComponent(\".claude/projects\", isDirectory: true),\n            ]\n        }()\n\n        return claudeRoots.contains(where: hasAnyJsonl(in:))\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/SettingsStore.swift",
    "content": "import AppKit\nimport CodexBarCore\nimport Observation\nimport ServiceManagement\n\nenum RefreshFrequency: String, CaseIterable, Identifiable {\n    case manual\n    case oneMinute\n    case twoMinutes\n    case fiveMinutes\n    case fifteenMinutes\n    case thirtyMinutes\n\n    var id: String {\n        self.rawValue\n    }\n\n    var seconds: TimeInterval? {\n        switch self {\n        case .manual: nil\n        case .oneMinute: 60\n        case .twoMinutes: 120\n        case .fiveMinutes: 300\n        case .fifteenMinutes: 900\n        case .thirtyMinutes: 1800\n        }\n    }\n\n    var label: String {\n        switch self {\n        case .manual: \"Manual\"\n        case .oneMinute: \"1 min\"\n        case .twoMinutes: \"2 min\"\n        case .fiveMinutes: \"5 min\"\n        case .fifteenMinutes: \"15 min\"\n        case .thirtyMinutes: \"30 min\"\n        }\n    }\n}\n\nenum MenuBarMetricPreference: String, CaseIterable, Identifiable {\n    case automatic\n    case primary\n    case secondary\n    case average\n\n    var id: String {\n        self.rawValue\n    }\n\n    var label: String {\n        switch self {\n        case .automatic: \"Automatic\"\n        case .primary: \"Primary\"\n        case .secondary: \"Secondary\"\n        case .average: \"Average\"\n        }\n    }\n}\n\n@MainActor\n@Observable\nfinal class SettingsStore {\n    static let sharedDefaults = UserDefaults(suiteName: \"group.com.steipete.codexbar\")\n    static let mergedOverviewProviderLimit = 3\n    static let isRunningTests: Bool = {\n        let env = ProcessInfo.processInfo.environment\n        if env[\"XCTestConfigurationFilePath\"] != nil { return true }\n        if env[\"TESTING_LIBRARY_VERSION\"] != nil { return true }\n        if env[\"SWIFT_TESTING\"] != nil { return true }\n        return NSClassFromString(\"XCTestCase\") != nil\n    }()\n\n    @ObservationIgnored let userDefaults: UserDefaults\n    @ObservationIgnored let configStore: CodexBarConfigStore\n    @ObservationIgnored var config: CodexBarConfig\n    @ObservationIgnored var configPersistTask: Task<Void, Never>?\n    @ObservationIgnored var configLoading = false\n    @ObservationIgnored var tokenAccountsLoaded = false\n    var defaultsState: SettingsDefaultsState\n    var configRevision: Int = 0\n    var providerOrder: [UsageProvider] = []\n    var providerEnablement: [UsageProvider: Bool] = [:]\n\n    init(\n        userDefaults: UserDefaults = .standard,\n        configStore: CodexBarConfigStore = CodexBarConfigStore(),\n        zaiTokenStore: any ZaiTokenStoring = KeychainZaiTokenStore(),\n        syntheticTokenStore: any SyntheticTokenStoring = KeychainSyntheticTokenStore(),\n        codexCookieStore: any CookieHeaderStoring = KeychainCookieHeaderStore(\n            account: \"codex-cookie\",\n            promptKind: .codexCookie),\n        claudeCookieStore: any CookieHeaderStoring = KeychainCookieHeaderStore(\n            account: \"claude-cookie\",\n            promptKind: .claudeCookie),\n        cursorCookieStore: any CookieHeaderStoring = KeychainCookieHeaderStore(\n            account: \"cursor-cookie\",\n            promptKind: .cursorCookie),\n        opencodeCookieStore: any CookieHeaderStoring = KeychainCookieHeaderStore(\n            account: \"opencode-cookie\",\n            promptKind: .opencodeCookie),\n        factoryCookieStore: any CookieHeaderStoring = KeychainCookieHeaderStore(\n            account: \"factory-cookie\",\n            promptKind: .factoryCookie),\n        minimaxCookieStore: any MiniMaxCookieStoring = KeychainMiniMaxCookieStore(),\n        minimaxAPITokenStore: any MiniMaxAPITokenStoring = KeychainMiniMaxAPITokenStore(),\n        kimiTokenStore: any KimiTokenStoring = KeychainKimiTokenStore(),\n        kimiK2TokenStore: any KimiK2TokenStoring = KeychainKimiK2TokenStore(),\n        augmentCookieStore: any CookieHeaderStoring = KeychainCookieHeaderStore(\n            account: \"augment-cookie\",\n            promptKind: .augmentCookie),\n        ampCookieStore: any CookieHeaderStoring = KeychainCookieHeaderStore(\n            account: \"amp-cookie\",\n            promptKind: .ampCookie),\n        copilotTokenStore: any CopilotTokenStoring = KeychainCopilotTokenStore(),\n        tokenAccountStore: any ProviderTokenAccountStoring = FileTokenAccountStore())\n    {\n        let legacyStores = CodexBarConfigMigrator.LegacyStores(\n            zaiTokenStore: zaiTokenStore,\n            syntheticTokenStore: syntheticTokenStore,\n            codexCookieStore: codexCookieStore,\n            claudeCookieStore: claudeCookieStore,\n            cursorCookieStore: cursorCookieStore,\n            opencodeCookieStore: opencodeCookieStore,\n            factoryCookieStore: factoryCookieStore,\n            minimaxCookieStore: minimaxCookieStore,\n            minimaxAPITokenStore: minimaxAPITokenStore,\n            kimiTokenStore: kimiTokenStore,\n            kimiK2TokenStore: kimiK2TokenStore,\n            augmentCookieStore: augmentCookieStore,\n            ampCookieStore: ampCookieStore,\n            copilotTokenStore: copilotTokenStore,\n            tokenAccountStore: tokenAccountStore)\n        let config = CodexBarConfigMigrator.loadOrMigrate(\n            configStore: configStore,\n            userDefaults: userDefaults,\n            stores: legacyStores)\n        self.userDefaults = userDefaults\n        self.configStore = configStore\n        self.config = config\n        self.configLoading = true\n        self.defaultsState = Self.loadDefaultsState(userDefaults: userDefaults)\n        self.updateProviderState(config: config)\n        self.configLoading = false\n        CodexBarLog.setFileLoggingEnabled(self.debugFileLoggingEnabled)\n        userDefaults.removeObject(forKey: \"showCodexUsage\")\n        userDefaults.removeObject(forKey: \"showClaudeUsage\")\n        LaunchAtLoginManager.setEnabled(self.launchAtLogin)\n        self.runInitialProviderDetectionIfNeeded()\n        self.ensureAlibabaProviderAutoEnabledIfNeeded()\n        self.applyTokenCostDefaultIfNeeded()\n        if self.claudeUsageDataSource != .cli { self.claudeWebExtrasEnabled = false }\n        self.openAIWebAccessEnabled = self.codexCookieSource.isEnabled\n        Self.sharedDefaults?.set(self.debugDisableKeychainAccess, forKey: \"debugDisableKeychainAccess\")\n        KeychainAccessGate.isDisabled = self.debugDisableKeychainAccess\n    }\n}\n\nextension SettingsStore {\n    private static func loadDefaultsState(userDefaults: UserDefaults) -> SettingsDefaultsState {\n        let refreshRaw = userDefaults.string(forKey: \"refreshFrequency\") ?? RefreshFrequency.fiveMinutes.rawValue\n        let refreshFrequency = RefreshFrequency(rawValue: refreshRaw) ?? .fiveMinutes\n        let launchAtLogin = userDefaults.object(forKey: \"launchAtLogin\") as? Bool ?? false\n        let debugMenuEnabled = userDefaults.object(forKey: \"debugMenuEnabled\") as? Bool ?? false\n        let debugDisableKeychainAccess: Bool = {\n            if let stored = userDefaults.object(forKey: \"debugDisableKeychainAccess\") as? Bool {\n                return stored\n            }\n            if let shared = Self.sharedDefaults?.object(forKey: \"debugDisableKeychainAccess\") as? Bool {\n                userDefaults.set(shared, forKey: \"debugDisableKeychainAccess\")\n                return shared\n            }\n            return false\n        }()\n        let debugFileLoggingEnabled = userDefaults.object(forKey: \"debugFileLoggingEnabled\") as? Bool ?? false\n        let debugLogLevelRaw = userDefaults.string(forKey: \"debugLogLevel\") ?? CodexBarLog.Level.verbose.rawValue\n        if userDefaults.string(forKey: \"debugLogLevel\") == nil {\n            userDefaults.set(debugLogLevelRaw, forKey: \"debugLogLevel\")\n        }\n        let debugLoadingPatternRaw = userDefaults.string(forKey: \"debugLoadingPattern\")\n        let debugKeepCLISessionsAlive = userDefaults.object(forKey: \"debugKeepCLISessionsAlive\") as? Bool ?? false\n        let statusChecksEnabled = userDefaults.object(forKey: \"statusChecksEnabled\") as? Bool ?? true\n        let sessionQuotaDefault = userDefaults.object(forKey: \"sessionQuotaNotificationsEnabled\") as? Bool\n        let sessionQuotaNotificationsEnabled = sessionQuotaDefault ?? true\n        if sessionQuotaDefault == nil {\n            userDefaults.set(true, forKey: \"sessionQuotaNotificationsEnabled\")\n        }\n        let usageBarsShowUsed = userDefaults.object(forKey: \"usageBarsShowUsed\") as? Bool ?? false\n        let resetTimesShowAbsolute = userDefaults.object(forKey: \"resetTimesShowAbsolute\") as? Bool ?? false\n        let menuBarShowsBrandIconWithPercent = userDefaults.object(\n            forKey: \"menuBarShowsBrandIconWithPercent\") as? Bool ?? false\n        let menuBarDisplayModeRaw = userDefaults.string(forKey: \"menuBarDisplayMode\")\n            ?? MenuBarDisplayMode.percent.rawValue\n        let historicalTrackingEnabled = userDefaults.object(forKey: \"historicalTrackingEnabled\") as? Bool ?? false\n        let showAllTokenAccountsInMenu = userDefaults.object(forKey: \"showAllTokenAccountsInMenu\") as? Bool ?? false\n        let storedPreferences = userDefaults.dictionary(forKey: \"menuBarMetricPreferences\") as? [String: String] ?? [:]\n        var resolvedPreferences = storedPreferences\n        if resolvedPreferences.isEmpty,\n           let menuBarMetricRaw = userDefaults.string(forKey: \"menuBarMetricPreference\"),\n           let legacyPreference = MenuBarMetricPreference(rawValue: menuBarMetricRaw)\n        {\n            resolvedPreferences = Dictionary(\n                uniqueKeysWithValues: UsageProvider.allCases.map { ($0.rawValue, legacyPreference.rawValue) })\n        }\n        let costUsageEnabled = userDefaults.object(forKey: \"tokenCostUsageEnabled\") as? Bool ?? false\n        let hidePersonalInfo = userDefaults.object(forKey: \"hidePersonalInfo\") as? Bool ?? false\n        let randomBlinkEnabled = userDefaults.object(forKey: \"randomBlinkEnabled\") as? Bool ?? false\n        let menuBarShowsHighestUsage = userDefaults.object(forKey: \"menuBarShowsHighestUsage\") as? Bool ?? false\n        let claudeOAuthKeychainPromptModeRaw = userDefaults.string(forKey: \"claudeOAuthKeychainPromptMode\")\n        let claudeOAuthKeychainReadStrategyRaw = userDefaults.string(forKey: \"claudeOAuthKeychainReadStrategy\")\n        let claudeWebExtrasEnabledRaw = userDefaults.object(forKey: \"claudeWebExtrasEnabled\") as? Bool ?? false\n        let creditsExtrasDefault = userDefaults.object(forKey: \"showOptionalCreditsAndExtraUsage\") as? Bool\n        let showOptionalCreditsAndExtraUsage = creditsExtrasDefault ?? true\n        if creditsExtrasDefault == nil { userDefaults.set(true, forKey: \"showOptionalCreditsAndExtraUsage\") }\n        let openAIWebAccessDefault = userDefaults.object(forKey: \"openAIWebAccessEnabled\") as? Bool\n        let openAIWebAccessEnabled = openAIWebAccessDefault ?? true\n        if openAIWebAccessDefault == nil { userDefaults.set(true, forKey: \"openAIWebAccessEnabled\") }\n        let jetbrainsIDEBasePath = userDefaults.string(forKey: \"jetbrainsIDEBasePath\") ?? \"\"\n        let mergeIcons = userDefaults.object(forKey: \"mergeIcons\") as? Bool ?? true\n        let switcherShowsIcons = userDefaults.object(forKey: \"switcherShowsIcons\") as? Bool ?? true\n        let mergedMenuLastSelectedWasOverview = userDefaults.object(\n            forKey: \"mergedMenuLastSelectedWasOverview\") as? Bool ?? false\n        let mergedOverviewSelectedProvidersRaw = userDefaults.array(\n            forKey: \"mergedOverviewSelectedProviders\") as? [String] ?? []\n        let selectedMenuProviderRaw = userDefaults.string(forKey: \"selectedMenuProvider\")\n        let providerDetectionCompleted = userDefaults.object(forKey: \"providerDetectionCompleted\") as? Bool ?? false\n\n        return SettingsDefaultsState(\n            refreshFrequency: refreshFrequency,\n            launchAtLogin: launchAtLogin,\n            debugMenuEnabled: debugMenuEnabled,\n            debugDisableKeychainAccess: debugDisableKeychainAccess,\n            debugFileLoggingEnabled: debugFileLoggingEnabled,\n            debugLogLevelRaw: debugLogLevelRaw,\n            debugLoadingPatternRaw: debugLoadingPatternRaw,\n            debugKeepCLISessionsAlive: debugKeepCLISessionsAlive,\n            statusChecksEnabled: statusChecksEnabled,\n            sessionQuotaNotificationsEnabled: sessionQuotaNotificationsEnabled,\n            usageBarsShowUsed: usageBarsShowUsed,\n            resetTimesShowAbsolute: resetTimesShowAbsolute,\n            menuBarShowsBrandIconWithPercent: menuBarShowsBrandIconWithPercent,\n            menuBarDisplayModeRaw: menuBarDisplayModeRaw,\n            historicalTrackingEnabled: historicalTrackingEnabled,\n            showAllTokenAccountsInMenu: showAllTokenAccountsInMenu,\n            menuBarMetricPreferencesRaw: resolvedPreferences,\n            costUsageEnabled: costUsageEnabled,\n            hidePersonalInfo: hidePersonalInfo,\n            randomBlinkEnabled: randomBlinkEnabled,\n            menuBarShowsHighestUsage: menuBarShowsHighestUsage,\n            claudeOAuthKeychainPromptModeRaw: claudeOAuthKeychainPromptModeRaw,\n            claudeOAuthKeychainReadStrategyRaw: claudeOAuthKeychainReadStrategyRaw,\n            claudeWebExtrasEnabledRaw: claudeWebExtrasEnabledRaw,\n            showOptionalCreditsAndExtraUsage: showOptionalCreditsAndExtraUsage,\n            openAIWebAccessEnabled: openAIWebAccessEnabled,\n            jetbrainsIDEBasePath: jetbrainsIDEBasePath,\n            mergeIcons: mergeIcons,\n            switcherShowsIcons: switcherShowsIcons,\n            mergedMenuLastSelectedWasOverview: mergedMenuLastSelectedWasOverview,\n            mergedOverviewSelectedProvidersRaw: mergedOverviewSelectedProvidersRaw,\n            selectedMenuProviderRaw: selectedMenuProviderRaw,\n            providerDetectionCompleted: providerDetectionCompleted)\n    }\n}\n\nextension SettingsStore {\n    var configSnapshot: CodexBarConfig {\n        _ = self.configRevision\n        return self.config\n    }\n\n    func updateProviderState(config: CodexBarConfig) {\n        let rawOrder = config.providers.map(\\.id.rawValue)\n        self.providerOrder = Self.effectiveProviderOrder(raw: rawOrder)\n        let metadata = ProviderDescriptorRegistry.metadata\n        var enablement: [UsageProvider: Bool] = [:]\n        enablement.reserveCapacity(metadata.count)\n        for provider in UsageProvider.allCases {\n            let defaultEnabled = metadata[provider]?.defaultEnabled ?? false\n            enablement[provider] = config.providerConfig(for: provider)?.enabled ?? defaultEnabled\n        }\n        self.providerEnablement = enablement\n    }\n\n    func orderedProviders() -> [UsageProvider] {\n        if self.providerOrder.isEmpty {\n            self.updateProviderState(config: self.configSnapshot)\n        }\n        return self.providerOrder\n    }\n\n    func moveProvider(fromOffsets: IndexSet, toOffset: Int) {\n        var order = self.orderedProviders()\n        order.move(fromOffsets: fromOffsets, toOffset: toOffset)\n        self.setProviderOrder(order)\n    }\n\n    func isProviderEnabled(provider: UsageProvider, metadata: ProviderMetadata) -> Bool {\n        self.providerEnablement[provider] ?? metadata.defaultEnabled\n    }\n\n    func isProviderEnabledCached(\n        provider: UsageProvider,\n        metadataByProvider: [UsageProvider: ProviderMetadata]) -> Bool\n    {\n        let defaultEnabled = metadataByProvider[provider]?.defaultEnabled ?? false\n        return self.providerEnablement[provider] ?? defaultEnabled\n    }\n\n    func enabledProvidersOrdered(metadataByProvider: [UsageProvider: ProviderMetadata]) -> [UsageProvider] {\n        _ = metadataByProvider\n        return self.orderedProviders().filter { self.providerEnablement[$0] ?? false }\n    }\n\n    func setProviderEnabled(provider: UsageProvider, metadata _: ProviderMetadata, enabled: Bool) {\n        CodexBarLog.logger(LogCategories.settings).debug(\n            \"Provider toggle updated\",\n            metadata: [\"provider\": provider.rawValue, \"enabled\": \"\\(enabled)\"])\n        self.updateProviderConfig(provider: provider) { entry in\n            entry.enabled = enabled\n        }\n    }\n\n    func rerunProviderDetection() {\n        self.runInitialProviderDetectionIfNeeded(force: true)\n    }\n}\n\nextension SettingsStore {\n    private static func effectiveProviderOrder(raw: [String]) -> [UsageProvider] {\n        var seen: Set<UsageProvider> = []\n        var ordered: [UsageProvider] = []\n\n        for rawValue in raw {\n            guard let provider = UsageProvider(rawValue: rawValue) else { continue }\n            guard !seen.contains(provider) else { continue }\n            seen.insert(provider)\n            ordered.append(provider)\n        }\n\n        if ordered.isEmpty {\n            ordered = UsageProvider.allCases\n            seen = Set(ordered)\n        }\n\n        if !seen.contains(.factory), let zaiIndex = ordered.firstIndex(of: .zai) {\n            ordered.insert(.factory, at: zaiIndex)\n            seen.insert(.factory)\n        }\n\n        if !seen.contains(.minimax), let zaiIndex = ordered.firstIndex(of: .zai) {\n            let insertIndex = ordered.index(after: zaiIndex)\n            ordered.insert(.minimax, at: insertIndex)\n            seen.insert(.minimax)\n        }\n\n        for provider in UsageProvider.allCases where !seen.contains(provider) {\n            ordered.append(provider)\n        }\n\n        return ordered\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/SettingsStoreState.swift",
    "content": "import Foundation\n\nstruct SettingsDefaultsState {\n    var refreshFrequency: RefreshFrequency\n    var launchAtLogin: Bool\n    var debugMenuEnabled: Bool\n    var debugDisableKeychainAccess: Bool\n    var debugFileLoggingEnabled: Bool\n    var debugLogLevelRaw: String?\n    var debugLoadingPatternRaw: String?\n    var debugKeepCLISessionsAlive: Bool\n    var statusChecksEnabled: Bool\n    var sessionQuotaNotificationsEnabled: Bool\n    var usageBarsShowUsed: Bool\n    var resetTimesShowAbsolute: Bool\n    var menuBarShowsBrandIconWithPercent: Bool\n    var menuBarDisplayModeRaw: String?\n    var historicalTrackingEnabled: Bool\n    var showAllTokenAccountsInMenu: Bool\n    var menuBarMetricPreferencesRaw: [String: String]\n    var costUsageEnabled: Bool\n    var hidePersonalInfo: Bool\n    var randomBlinkEnabled: Bool\n    var menuBarShowsHighestUsage: Bool\n    var claudeOAuthKeychainPromptModeRaw: String?\n    var claudeOAuthKeychainReadStrategyRaw: String?\n    var claudeWebExtrasEnabledRaw: Bool\n    var showOptionalCreditsAndExtraUsage: Bool\n    var openAIWebAccessEnabled: Bool\n    var jetbrainsIDEBasePath: String\n    var mergeIcons: Bool\n    var switcherShowsIcons: Bool\n    var mergedMenuLastSelectedWasOverview: Bool\n    var mergedOverviewSelectedProvidersRaw: [String]\n    var selectedMenuProviderRaw: String?\n    var providerDetectionCompleted: Bool\n}\n"
  },
  {
    "path": "Sources/CodexBar/StatusItemController+Actions.swift",
    "content": "import AppKit\nimport CodexBarCore\n\nextension StatusItemController {\n    // MARK: - Actions reachable from menus\n\n    func refreshStore(forceTokenUsage: Bool) {\n        Task {\n            await ProviderInteractionContext.$current.withValue(.userInitiated) {\n                await self.store.refresh(forceTokenUsage: forceTokenUsage)\n            }\n        }\n    }\n\n    @objc func refreshNow() {\n        self.refreshStore(forceTokenUsage: true)\n    }\n\n    @objc func refreshAugmentSession() {\n        Task {\n            await self.store.forceRefreshAugmentSession()\n            // Also trigger a full refresh to update the menu and clear any stale errors\n            await ProviderInteractionContext.$current.withValue(.userInitiated) {\n                await self.store.refresh(forceTokenUsage: false)\n            }\n        }\n    }\n\n    @objc func installUpdate() {\n        self.updater.checkForUpdates(nil)\n    }\n\n    @objc func openDashboard() {\n        let preferred = self.lastMenuProvider\n            ?? (self.store.isEnabled(.codex) ? .codex : self.store.enabledProviders().first)\n\n        let provider = preferred ?? .codex\n        guard let url = self.dashboardURL(for: provider) else { return }\n        NSWorkspace.shared.open(url)\n    }\n\n    func dashboardURL(for provider: UsageProvider) -> URL? {\n        if provider == .alibaba {\n            return self.settings.alibabaCodingPlanAPIRegion.dashboardURL\n        }\n\n        let meta = self.store.metadata(for: provider)\n        let urlString: String? = if provider == .claude, self.store.isClaudeSubscription() {\n            meta.subscriptionDashboardURL ?? meta.dashboardURL\n        } else {\n            meta.dashboardURL\n        }\n\n        guard let urlString else { return nil }\n        return URL(string: urlString)\n    }\n\n    @objc func openCreditsPurchase() {\n        let preferred = self.lastMenuProvider\n            ?? (self.store.isEnabled(.codex) ? .codex : self.store.enabledProviders().first)\n        let provider = preferred ?? .codex\n        guard provider == .codex else { return }\n\n        let dashboardURL = self.store.metadata(for: .codex).dashboardURL\n        let purchaseURL = Self.sanitizedCreditsPurchaseURL(self.store.openAIDashboard?.creditsPurchaseURL)\n        let urlString = purchaseURL ?? dashboardURL\n        guard let urlString,\n              let url = URL(string: urlString) else { return }\n\n        let autoStart = true\n        let accountEmail = self.store.codexAccountEmailForOpenAIDashboard()\n        let controller = self.creditsPurchaseWindow ?? OpenAICreditsPurchaseWindowController()\n        controller.show(purchaseURL: url, accountEmail: accountEmail, autoStartPurchase: autoStart)\n        self.creditsPurchaseWindow = controller\n    }\n\n    private static func sanitizedCreditsPurchaseURL(_ raw: String?) -> String? {\n        guard let raw, let url = URL(string: raw) else { return nil }\n        guard let host = url.host?.lowercased(), host.contains(\"chatgpt.com\") else { return nil }\n        let path = url.path.lowercased()\n        let allowed = [\"settings\", \"usage\", \"billing\", \"credits\"]\n        guard allowed.contains(where: { path.contains($0) }) else { return nil }\n        return url.absoluteString\n    }\n\n    @objc func openStatusPage() {\n        let preferred = self.lastMenuProvider\n            ?? (self.store.isEnabled(.codex) ? .codex : self.store.enabledProviders().first)\n\n        let provider = preferred ?? .codex\n        let meta = self.store.metadata(for: provider)\n        let urlString = meta.statusPageURL ?? meta.statusLinkURL\n        guard let urlString, let url = URL(string: urlString) else { return }\n        NSWorkspace.shared.open(url)\n    }\n\n    @objc func openTerminalCommand(_ sender: NSMenuItem) {\n        let command = sender.representedObject as? String ?? \"claude\"\n        Self.openTerminal(command: command)\n    }\n\n    @objc func openLoginToProvider(_ sender: NSMenuItem) {\n        guard let urlString = sender.representedObject as? String,\n              let url = URL(string: urlString) else { return }\n        NSWorkspace.shared.open(url)\n    }\n\n    @objc func runSwitchAccount(_ sender: NSMenuItem) {\n        if self.loginTask != nil {\n            self.loginLogger.info(\"Switch Account tap ignored: login already in-flight\")\n            return\n        }\n\n        let rawProvider = sender.representedObject as? String\n        let provider = rawProvider.flatMap(UsageProvider.init(rawValue:)) ?? self.lastMenuProvider ?? .codex\n        self.loginLogger.info(\"Switch Account tapped\", metadata: [\"provider\": provider.rawValue])\n\n        self.loginTask = Task { @MainActor [weak self] in\n            guard let self else { return }\n            defer {\n                self.activeLoginProvider = nil\n                self.loginTask = nil\n            }\n            self.activeLoginProvider = provider\n            self.loginPhase = .requesting\n            self.loginLogger.info(\"Starting login task\", metadata: [\"provider\": provider.rawValue])\n\n            let shouldRefresh = await self.runLoginFlow(provider: provider)\n            if shouldRefresh {\n                await ProviderInteractionContext.$current.withValue(.userInitiated) {\n                    await self.store.refresh()\n                }\n                self.loginLogger.info(\"Triggered refresh after login\", metadata: [\"provider\": provider.rawValue])\n            }\n        }\n    }\n\n    @objc func showSettingsGeneral() {\n        self.openSettings(tab: .general)\n    }\n\n    @objc func showSettingsAbout() {\n        self.openSettings(tab: .about)\n    }\n\n    func openMenuFromShortcut() {\n        if self.shouldMergeIcons {\n            self.statusItem.button?.performClick(nil)\n            return\n        }\n\n        let provider = self.resolvedShortcutProvider()\n        // Use the lazy accessor to ensure the item exists\n        let item = self.lazyStatusItem(for: provider)\n        item.button?.performClick(nil)\n    }\n\n    private func openSettings(tab: PreferencesTab) {\n        DispatchQueue.main.async {\n            self.preferencesSelection.tab = tab\n            NSApp.activate(ignoringOtherApps: true)\n            NotificationCenter.default.post(\n                name: .codexbarOpenSettings,\n                object: nil,\n                userInfo: [\"tab\": tab.rawValue])\n        }\n    }\n\n    @objc func quit() {\n        NSApp.terminate(nil)\n    }\n\n    @objc func copyError(_ sender: NSMenuItem) {\n        if let err = sender.representedObject as? String {\n            let pb = NSPasteboard.general\n            pb.clearContents()\n            pb.setString(err, forType: .string)\n        }\n    }\n\n    private static func openTerminal(command: String) {\n        let escaped = command\n            .replacingOccurrences(of: \"\\\\\\\\\", with: \"\\\\\\\\\\\\\\\\\")\n            .replacingOccurrences(of: \"\\\"\", with: \"\\\\\\\"\")\n        let script = \"\"\"\n        tell application \"Terminal\"\n            activate\n            do script \"\\(escaped)\"\n        end tell\n        \"\"\"\n        if let appleScript = NSAppleScript(source: script) {\n            var error: NSDictionary?\n            appleScript.executeAndReturnError(&error)\n            if let error {\n                CodexBarLog.logger(LogCategories.terminal).error(\n                    \"Failed to open Terminal\",\n                    metadata: [\"error\": String(describing: error)])\n            }\n        }\n    }\n\n    private func resolvedShortcutProvider() -> UsageProvider {\n        if let last = self.lastMenuProvider, self.isEnabled(last) {\n            return last\n        }\n        if let first = self.store.enabledProviders().first {\n            return first\n        }\n        return .codex\n    }\n\n    func presentCodexLoginResult(_ result: CodexLoginRunner.Result) {\n        switch result.outcome {\n        case .success:\n            return\n        case .missingBinary:\n            self.presentLoginAlert(\n                title: \"Codex CLI not found\",\n                message: \"Install the Codex CLI (npm i -g @openai/codex) and try again.\")\n        case let .launchFailed(message):\n            self.presentLoginAlert(title: \"Could not start codex login\", message: message)\n        case .timedOut:\n            self.presentLoginAlert(\n                title: \"Codex login timed out\",\n                message: self.trimmedLoginOutput(result.output))\n        case let .failed(status):\n            let statusLine = \"codex login exited with status \\(status).\"\n            let message = self.trimmedLoginOutput(result.output.isEmpty ? statusLine : result.output)\n            self.presentLoginAlert(title: \"Codex login failed\", message: message)\n        }\n    }\n\n    func presentClaudeLoginResult(_ result: ClaudeLoginRunner.Result) {\n        switch result.outcome {\n        case .success:\n            return\n        case .missingBinary:\n            self.presentLoginAlert(\n                title: \"Claude CLI not found\",\n                message: \"Install the Claude CLI (npm i -g @anthropic-ai/claude-code) and try again.\")\n        case let .launchFailed(message):\n            self.presentLoginAlert(title: \"Could not start claude /login\", message: message)\n        case .timedOut:\n            self.presentLoginAlert(\n                title: \"Claude login timed out\",\n                message: self.trimmedLoginOutput(result.output))\n        case let .failed(status):\n            let statusLine = \"claude /login exited with status \\(status).\"\n            let message = self.trimmedLoginOutput(result.output.isEmpty ? statusLine : result.output)\n            self.presentLoginAlert(title: \"Claude login failed\", message: message)\n        }\n    }\n\n    func describe(_ outcome: CodexLoginRunner.Result.Outcome) -> String {\n        switch outcome {\n        case .success: \"success\"\n        case .timedOut: \"timedOut\"\n        case let .failed(status): \"failed(status: \\(status))\"\n        case .missingBinary: \"missingBinary\"\n        case let .launchFailed(message): \"launchFailed(\\(message))\"\n        }\n    }\n\n    func describe(_ outcome: ClaudeLoginRunner.Result.Outcome) -> String {\n        switch outcome {\n        case .success: \"success\"\n        case .timedOut: \"timedOut\"\n        case let .failed(status): \"failed(status: \\(status))\"\n        case .missingBinary: \"missingBinary\"\n        case let .launchFailed(message): \"launchFailed(\\(message))\"\n        }\n    }\n\n    func describe(_ outcome: GeminiLoginRunner.Result.Outcome) -> String {\n        switch outcome {\n        case .success: \"success\"\n        case .missingBinary: \"missingBinary\"\n        case let .launchFailed(message): \"launchFailed(\\(message))\"\n        }\n    }\n\n    func presentGeminiLoginResult(_ result: GeminiLoginRunner.Result) {\n        guard let info = Self.geminiLoginAlertInfo(for: result) else { return }\n        self.presentLoginAlert(title: info.title, message: info.message)\n    }\n\n    struct LoginAlertInfo: Equatable {\n        let title: String\n        let message: String\n    }\n\n    nonisolated static func geminiLoginAlertInfo(for result: GeminiLoginRunner.Result) -> LoginAlertInfo? {\n        switch result.outcome {\n        case .success:\n            nil\n        case .missingBinary:\n            LoginAlertInfo(\n                title: \"Gemini CLI not found\",\n                message: \"Install the Gemini CLI (npm i -g @google/gemini-cli) and try again.\")\n        case let .launchFailed(message):\n            LoginAlertInfo(title: \"Could not open Terminal for Gemini\", message: message)\n        }\n    }\n\n    func presentLoginAlert(title: String, message: String) {\n        let alert = NSAlert()\n        alert.messageText = title\n        alert.informativeText = message\n        alert.alertStyle = .warning\n        alert.runModal()\n    }\n\n    private func trimmedLoginOutput(_ text: String) -> String {\n        let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)\n        let limit = 600\n        if trimmed.isEmpty { return \"No output captured.\" }\n        if trimmed.count <= limit { return trimmed }\n        let idx = trimmed.index(trimmed.startIndex, offsetBy: limit)\n        return \"\\(trimmed[..<idx])…\"\n    }\n\n    func postLoginNotification(for provider: UsageProvider) {\n        let name = ProviderDescriptorRegistry.descriptor(for: provider).metadata.displayName\n        let title = \"\\(name) login successful\"\n        let body = \"You can return to the app; authentication finished.\"\n        AppNotifications.shared.post(idPrefix: \"login-\\(provider.rawValue)\", title: title, body: body)\n    }\n\n    func presentCursorLoginResult(_ result: CursorLoginRunner.Result) {\n        switch result.outcome {\n        case .success:\n            return\n        case .cancelled:\n            // User closed the window; no alert needed\n            return\n        case let .failed(message):\n            self.presentLoginAlert(title: \"Cursor login failed\", message: message)\n        }\n    }\n\n    func describe(_ outcome: CursorLoginRunner.Result.Outcome) -> String {\n        switch outcome {\n        case .success: \"success\"\n        case .cancelled: \"cancelled\"\n        case let .failed(message): \"failed(\\(message))\"\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/StatusItemController+Animation.swift",
    "content": "import AppKit\nimport CodexBarCore\nimport QuartzCore\n\nextension StatusItemController {\n    private static let loadingPercentEpsilon = 0.0001\n    private static let blinkActiveTickInterval: Duration = .milliseconds(75)\n    private static let blinkIdleFallbackInterval: Duration = .seconds(1)\n\n    func needsMenuBarIconAnimation() -> Bool {\n        if self.shouldMergeIcons {\n            let primaryProvider = self.primaryProviderForUnifiedIcon()\n            return self.shouldAnimate(provider: primaryProvider)\n        }\n        return UsageProvider.allCases.contains { self.shouldAnimate(provider: $0) }\n    }\n\n    func updateBlinkingState() {\n        // During the loading animation, blink ticks can overwrite the animated menu bar icon and cause flicker.\n        if self.needsMenuBarIconAnimation() {\n            self.stopBlinking()\n            return\n        }\n\n        let blinkingEnabled = self.isBlinkingAllowed()\n        // Use display list so merged-mode visibility stays consistent with shouldMergeIcons.\n        let displayProviders = self.store.enabledProvidersForDisplay()\n        let anyEnabled = !displayProviders.isEmpty || self.store.debugForceAnimation\n        let anyVisible = UsageProvider.allCases.contains { self.isVisible($0) }\n        let mergeIcons = self.shouldMergeIcons\n        let shouldBlink = mergeIcons ? anyEnabled : anyVisible\n        if blinkingEnabled, shouldBlink {\n            if self.blinkTask == nil {\n                self.seedBlinkStatesIfNeeded()\n                self.blinkTask = Task { [weak self] in\n                    while !Task.isCancelled {\n                        let delay = await MainActor.run {\n                            self?.blinkTickSleepDuration(now: Date()) ?? Self.blinkIdleFallbackInterval\n                        }\n                        try? await Task.sleep(for: delay)\n                        await MainActor.run { self?.tickBlink() }\n                    }\n                }\n            }\n        } else {\n            self.stopBlinking()\n        }\n    }\n\n    private func seedBlinkStatesIfNeeded() {\n        let now = Date()\n        for provider in UsageProvider.allCases where self.blinkStates[provider] == nil {\n            self.blinkStates[provider] = BlinkState(nextBlink: now.addingTimeInterval(BlinkState.randomDelay()))\n        }\n    }\n\n    private func stopBlinking() {\n        self.blinkTask?.cancel()\n        self.blinkTask = nil\n        self.blinkAmounts.removeAll()\n        let phase: Double? = self.needsMenuBarIconAnimation() ? self.animationPhase : nil\n        if self.shouldMergeIcons {\n            self.applyIcon(phase: phase)\n        } else {\n            for provider in UsageProvider.allCases {\n                self.applyIcon(for: provider, phase: phase)\n            }\n        }\n    }\n\n    private func blinkTickSleepDuration(now: Date) -> Duration {\n        let mergeIcons = self.shouldMergeIcons\n        var nextWakeAt: Date?\n\n        for provider in UsageProvider.allCases {\n            let shouldRender = mergeIcons ? self.isEnabled(provider) : self.isVisible(provider)\n            guard shouldRender, !self.shouldAnimate(provider: provider, mergeIcons: mergeIcons) else { continue }\n\n            let state = self\n                .blinkStates[provider] ?? BlinkState(nextBlink: now.addingTimeInterval(BlinkState.randomDelay()))\n            if state.blinkStart != nil {\n                return Self.blinkActiveTickInterval\n            }\n\n            let candidate: Date = state.pendingSecondStart ?? state.nextBlink\n            if let current = nextWakeAt {\n                if candidate < current {\n                    nextWakeAt = candidate\n                }\n            } else {\n                nextWakeAt = candidate\n            }\n        }\n\n        guard let nextWakeAt else { return Self.blinkIdleFallbackInterval }\n        let delay = nextWakeAt.timeIntervalSince(now)\n        if delay <= 0 { return Self.blinkActiveTickInterval }\n        return .seconds(delay)\n    }\n\n    private func tickBlink(now: Date = .init()) {\n        guard self.isBlinkingAllowed(at: now) else {\n            self.stopBlinking()\n            return\n        }\n\n        let blinkDuration: TimeInterval = 0.36\n        let doubleBlinkChance = 0.18\n        let doubleDelayRange: ClosedRange<TimeInterval> = 0.22...0.34\n        // Cache merge state once per tick to avoid repeated enabled-provider lookups.\n        let mergeIcons = self.shouldMergeIcons\n\n        for provider in UsageProvider.allCases {\n            let shouldRender = mergeIcons ? self.isEnabled(provider) : self.isVisible(provider)\n            guard shouldRender, !self.shouldAnimate(provider: provider, mergeIcons: mergeIcons) else {\n                self.clearMotion(for: provider)\n                continue\n            }\n\n            var state = self\n                .blinkStates[provider] ?? BlinkState(nextBlink: now.addingTimeInterval(BlinkState.randomDelay()))\n\n            if let pendingSecond = state.pendingSecondStart, now >= pendingSecond {\n                state.blinkStart = now\n                state.pendingSecondStart = nil\n            }\n\n            if let start = state.blinkStart {\n                let elapsed = now.timeIntervalSince(start)\n                if elapsed >= blinkDuration {\n                    state.blinkStart = nil\n                    if let pending = state.pendingSecondStart, now < pending {\n                        // Wait for the planned double-blink.\n                    } else {\n                        state.pendingSecondStart = nil\n                        state.nextBlink = now.addingTimeInterval(BlinkState.randomDelay())\n                    }\n                    self.clearMotion(for: provider)\n                } else {\n                    let progress = max(0, min(elapsed / blinkDuration, 1))\n                    let symmetric = progress < 0.5 ? progress * 2 : (1 - progress) * 2\n                    let eased = pow(symmetric, 2.2) // slightly punchier than smoothstep\n                    self.assignMotion(amount: CGFloat(eased), for: provider, effect: state.effect)\n                }\n            } else if now >= state.nextBlink {\n                state.blinkStart = now\n                state.effect = self.randomEffect(for: provider)\n                if state.effect == .blink, Double.random(in: 0...1) < doubleBlinkChance {\n                    state.pendingSecondStart = now.addingTimeInterval(Double.random(in: doubleDelayRange))\n                }\n                self.clearMotion(for: provider)\n            } else {\n                self.clearMotion(for: provider)\n            }\n\n            self.blinkStates[provider] = state\n            if !mergeIcons {\n                self.applyIcon(for: provider, phase: nil)\n            }\n        }\n        if mergeIcons {\n            let phase: Double? = self.needsMenuBarIconAnimation() ? self.animationPhase : nil\n            self.applyIcon(phase: phase)\n        }\n    }\n\n    private func blinkAmount(for provider: UsageProvider) -> CGFloat {\n        guard self.isBlinkingAllowed() else { return 0 }\n        return self.blinkAmounts[provider] ?? 0\n    }\n\n    private func wiggleAmount(for provider: UsageProvider) -> CGFloat {\n        guard self.isBlinkingAllowed() else { return 0 }\n        return self.wiggleAmounts[provider] ?? 0\n    }\n\n    private func tiltAmount(for provider: UsageProvider) -> CGFloat {\n        guard self.isBlinkingAllowed() else { return 0 }\n        return self.tiltAmounts[provider] ?? 0\n    }\n\n    private func assignMotion(amount: CGFloat, for provider: UsageProvider, effect: MotionEffect) {\n        switch effect {\n        case .blink:\n            self.blinkAmounts[provider] = amount\n            self.wiggleAmounts[provider] = 0\n            self.tiltAmounts[provider] = 0\n        case .wiggle:\n            self.wiggleAmounts[provider] = amount\n            self.blinkAmounts[provider] = 0\n            self.tiltAmounts[provider] = 0\n        case .tilt:\n            self.tiltAmounts[provider] = amount\n            self.blinkAmounts[provider] = 0\n            self.wiggleAmounts[provider] = 0\n        }\n    }\n\n    private func clearMotion(for provider: UsageProvider) {\n        self.blinkAmounts[provider] = 0\n        self.wiggleAmounts[provider] = 0\n        self.tiltAmounts[provider] = 0\n    }\n\n    private func randomEffect(for provider: UsageProvider) -> MotionEffect {\n        if provider == .claude {\n            Bool.random() ? .blink : .wiggle\n        } else {\n            Bool.random() ? .blink : .tilt\n        }\n    }\n\n    private func isBlinkingAllowed(at date: Date = .init()) -> Bool {\n        if self.settings.randomBlinkEnabled { return true }\n        if let until = self.blinkForceUntil, until > date { return true }\n        self.blinkForceUntil = nil\n        return false\n    }\n\n    func applyIcon(phase: Double?) {\n        guard let button = self.statusItem.button else { return }\n\n        let style = self.store.iconStyle\n        let showUsed = self.settings.usageBarsShowUsed\n        let showBrandPercent = self.settings.menuBarShowsBrandIconWithPercent\n        let primaryProvider = self.primaryProviderForUnifiedIcon()\n        let snapshot = self.store.snapshot(for: primaryProvider)\n\n        // IconRenderer treats these values as a left-to-right \"progress fill\" percentage; depending on the\n        // user setting we pass either \"percent left\" or \"percent used\".\n        var primary = showUsed ? snapshot?.primary?.usedPercent : snapshot?.primary?.remainingPercent\n        var weekly = showUsed ? snapshot?.secondary?.usedPercent : snapshot?.secondary?.remainingPercent\n        if showUsed,\n           primaryProvider == .warp,\n           let remaining = snapshot?.secondary?.remainingPercent,\n           remaining <= 0\n        {\n            // Preserve Warp \"no bonus/exhausted bonus\" layout even in show-used mode.\n            weekly = 0\n        }\n        if showUsed,\n           primaryProvider == .warp,\n           let remaining = snapshot?.secondary?.remainingPercent,\n           remaining > 0,\n           weekly == 0\n        {\n            // In show-used mode, `0` means \"unused\", not \"missing\". Keep the weekly lane present.\n            weekly = Self.loadingPercentEpsilon\n        }\n        var credits: Double? = primaryProvider == .codex ? self.store.credits?.remaining : nil\n        var stale = self.store.isStale(provider: primaryProvider)\n        var morphProgress: Double?\n\n        let needsAnimation = self.needsMenuBarIconAnimation()\n        if let phase, needsAnimation {\n            var pattern = self.animationPattern\n            if style == .combined, pattern == .unbraid {\n                pattern = .cylon\n            }\n            if pattern == .unbraid {\n                morphProgress = pattern.value(phase: phase) / 100\n                primary = nil\n                weekly = nil\n                credits = nil\n                stale = false\n            } else {\n                // Keep loading animation layout stable: IconRenderer uses `weeklyRemaining > 0` to switch layouts,\n                // so hitting an exact 0 would flip between \"normal\" and \"weekly exhausted\" rendering.\n                primary = max(pattern.value(phase: phase), Self.loadingPercentEpsilon)\n                weekly = max(pattern.value(phase: phase + pattern.secondaryOffset), Self.loadingPercentEpsilon)\n                credits = nil\n                stale = false\n            }\n        }\n\n        let blink: CGFloat = style == .combined ? 0 : self.blinkAmount(for: primaryProvider)\n        let wiggle: CGFloat = style == .combined ? 0 : self.wiggleAmount(for: primaryProvider)\n        let tilt: CGFloat = style == .combined ? 0 : self.tiltAmount(for: primaryProvider) * .pi / 28\n\n        let statusIndicator: ProviderStatusIndicator = {\n            for provider in self.store.enabledProvidersForDisplay() {\n                let indicator = self.store.statusIndicator(for: provider)\n                if indicator.hasIssue { return indicator }\n            }\n            return .none\n        }()\n\n        if showBrandPercent,\n           let brand = ProviderBrandIcon.image(for: primaryProvider)\n        {\n            let displayText = self.menuBarDisplayText(for: primaryProvider, snapshot: snapshot)\n            self.setButtonImage(brand, for: button)\n            self.setButtonTitle(displayText, for: button)\n            return\n        }\n\n        if Self.shouldUseOpenRouterBrandFallback(provider: primaryProvider, snapshot: snapshot),\n           let brand = ProviderBrandIcon.image(for: primaryProvider)\n        {\n            self.setButtonTitle(nil, for: button)\n            self.setButtonImage(\n                Self.brandImageWithStatusOverlay(brand: brand, statusIndicator: statusIndicator),\n                for: button)\n            return\n        }\n\n        self.setButtonTitle(nil, for: button)\n        if let morphProgress {\n            let image = IconRenderer.makeMorphIcon(progress: morphProgress, style: style)\n            self.setButtonImage(image, for: button)\n        } else {\n            let image = IconRenderer.makeIcon(\n                primaryRemaining: primary,\n                weeklyRemaining: weekly,\n                creditsRemaining: credits,\n                stale: stale,\n                style: style,\n                blink: blink,\n                wiggle: wiggle,\n                tilt: tilt,\n                statusIndicator: statusIndicator)\n            self.setButtonImage(image, for: button)\n        }\n    }\n\n    func applyIcon(for provider: UsageProvider, phase: Double?) {\n        guard let button = self.statusItems[provider]?.button else { return }\n        let snapshot = self.store.snapshot(for: provider)\n        // IconRenderer treats these values as a left-to-right \"progress fill\" percentage; depending on the\n        // user setting we pass either \"percent left\" or \"percent used\".\n        let showUsed = self.settings.usageBarsShowUsed\n        let showBrandPercent = self.settings.menuBarShowsBrandIconWithPercent\n\n        if showBrandPercent,\n           let brand = ProviderBrandIcon.image(for: provider)\n        {\n            let displayText = self.menuBarDisplayText(for: provider, snapshot: snapshot)\n            self.setButtonImage(brand, for: button)\n            self.setButtonTitle(displayText, for: button)\n            return\n        }\n\n        if Self.shouldUseOpenRouterBrandFallback(provider: provider, snapshot: snapshot),\n           let brand = ProviderBrandIcon.image(for: provider)\n        {\n            self.setButtonTitle(nil, for: button)\n            self.setButtonImage(\n                Self.brandImageWithStatusOverlay(\n                    brand: brand,\n                    statusIndicator: self.store.statusIndicator(for: provider)),\n                for: button)\n            return\n        }\n        var primary = showUsed ? snapshot?.primary?.usedPercent : snapshot?.primary?.remainingPercent\n        var weekly = showUsed ? snapshot?.secondary?.usedPercent : snapshot?.secondary?.remainingPercent\n        if showUsed,\n           provider == .warp,\n           let remaining = snapshot?.secondary?.remainingPercent,\n           remaining <= 0\n        {\n            // Preserve Warp \"no bonus/exhausted bonus\" layout even in show-used mode.\n            weekly = 0\n        }\n        if showUsed,\n           provider == .warp,\n           let remaining = snapshot?.secondary?.remainingPercent,\n           remaining > 0,\n           weekly == 0\n        {\n            // In show-used mode, `0` means \"unused\", not \"missing\". Keep the weekly lane present.\n            weekly = Self.loadingPercentEpsilon\n        }\n        var credits: Double? = provider == .codex ? self.store.credits?.remaining : nil\n        var stale = self.store.isStale(provider: provider)\n        var morphProgress: Double?\n\n        if let phase, self.shouldAnimate(provider: provider) {\n            var pattern = self.animationPattern\n            if provider == .claude, pattern == .unbraid {\n                pattern = .cylon\n            }\n            if pattern == .unbraid {\n                morphProgress = pattern.value(phase: phase) / 100\n                primary = nil\n                weekly = nil\n                credits = nil\n                stale = false\n            } else {\n                // Keep loading animation layout stable: IconRenderer switches layouts at `weeklyRemaining == 0`.\n                primary = max(pattern.value(phase: phase), Self.loadingPercentEpsilon)\n                weekly = max(pattern.value(phase: phase + pattern.secondaryOffset), Self.loadingPercentEpsilon)\n                credits = nil\n                stale = false\n            }\n        }\n\n        let style: IconStyle = self.store.style(for: provider)\n        let isLoading = phase != nil && self.shouldAnimate(provider: provider)\n        let blink: CGFloat = {\n            guard isLoading, style == .warp, let phase else {\n                return self.blinkAmount(for: provider)\n            }\n            let normalized = (sin(phase * 3) + 1) / 2\n            return CGFloat(max(0, min(normalized, 1)))\n        }()\n        let wiggle = self.wiggleAmount(for: provider)\n        let tilt = self.tiltAmount(for: provider) * .pi / 28 // limit to ~6.4°\n        if let morphProgress {\n            let image = IconRenderer.makeMorphIcon(progress: morphProgress, style: style)\n            self.setButtonImage(image, for: button)\n        } else {\n            self.setButtonTitle(nil, for: button)\n            let image = IconRenderer.makeIcon(\n                primaryRemaining: primary,\n                weeklyRemaining: weekly,\n                creditsRemaining: credits,\n                stale: stale,\n                style: style,\n                blink: blink,\n                wiggle: wiggle,\n                tilt: tilt,\n                statusIndicator: self.store.statusIndicator(for: provider))\n            self.setButtonImage(image, for: button)\n        }\n    }\n\n    private func setButtonImage(_ image: NSImage, for button: NSStatusBarButton) {\n        if button.image === image { return }\n        button.image = image\n    }\n\n    private func setButtonTitle(_ title: String?, for button: NSStatusBarButton) {\n        let value = title ?? \"\"\n        if button.title != value {\n            button.title = value\n        }\n        let position: NSControl.ImagePosition = value.isEmpty ? .imageOnly : .imageLeft\n        if button.imagePosition != position {\n            button.imagePosition = position\n        }\n    }\n\n    func menuBarDisplayText(for provider: UsageProvider, snapshot: UsageSnapshot?) -> String? {\n        let percentWindow = self.menuBarPercentWindow(for: provider, snapshot: snapshot)\n        let mode = self.settings.menuBarDisplayMode\n        let now = Date()\n        let pace: UsagePace? = switch mode {\n        case .percent:\n            nil\n        case .pace, .both:\n            snapshot?.secondary.flatMap { window in\n                self.store.weeklyPace(provider: provider, window: window, now: now)\n            }\n        }\n        let displayText = MenuBarDisplayText.displayText(\n            mode: mode,\n            percentWindow: percentWindow,\n            pace: pace,\n            showUsed: self.settings.usageBarsShowUsed)\n\n        let sessionExhausted = (snapshot?.primary?.remainingPercent ?? 100) <= 0\n        let weeklyExhausted = (snapshot?.secondary?.remainingPercent ?? 100) <= 0\n\n        if provider == .codex,\n           mode == .percent,\n           !self.settings.usageBarsShowUsed,\n           sessionExhausted || weeklyExhausted,\n           let creditsRemaining = self.store.credits?.remaining,\n           creditsRemaining > 0\n        {\n            return UsageFormatter\n                .creditsString(from: creditsRemaining)\n                .replacingOccurrences(of: \" left\", with: \"\")\n        }\n\n        return displayText\n    }\n\n    private func menuBarPercentWindow(for provider: UsageProvider, snapshot: UsageSnapshot?) -> RateWindow? {\n        self.menuBarMetricWindow(for: provider, snapshot: snapshot)\n    }\n\n    private func primaryProviderForUnifiedIcon() -> UsageProvider {\n        // When \"show highest usage\" is enabled, auto-select the provider closest to rate limit.\n        if self.settings.menuBarShowsHighestUsage,\n           self.shouldMergeIcons,\n           let highest = self.store.providerWithHighestUsage()\n        {\n            return highest.provider\n        }\n        if self.shouldMergeIcons,\n           let selected = self.selectedMenuProvider,\n           self.store.isEnabled(selected)\n        {\n            return selected\n        }\n        for provider in UsageProvider.allCases {\n            if self.store.isEnabled(provider), self.store.snapshot(for: provider) != nil {\n                return provider\n            }\n        }\n        // Use availability-filtered list: fallback must pick a provider that can\n        // actually animate, otherwise shouldAnimate() fails on credential-less providers.\n        if let enabled = self.store.enabledProviders().first {\n            return enabled\n        }\n        return .codex\n    }\n\n    @objc func handleDebugBlinkNotification() {\n        self.forceBlinkNow()\n    }\n\n    private func forceBlinkNow() {\n        let now = Date()\n        self.blinkForceUntil = now.addingTimeInterval(0.6)\n        self.seedBlinkStatesIfNeeded()\n\n        for provider in UsageProvider.allCases {\n            let shouldBlink = self.shouldMergeIcons ? self.isEnabled(provider) : self.isVisible(provider)\n            guard shouldBlink, !self.shouldAnimate(provider: provider) else { continue }\n            var state = self\n                .blinkStates[provider] ?? BlinkState(nextBlink: now.addingTimeInterval(BlinkState.randomDelay()))\n            state.blinkStart = now\n            state.pendingSecondStart = nil\n            state.effect = self.randomEffect(for: provider)\n            state.nextBlink = now.addingTimeInterval(BlinkState.randomDelay())\n            self.blinkStates[provider] = state\n            self.assignMotion(amount: 0, for: provider, effect: state.effect)\n        }\n\n        // If the blink task is currently in a long idle sleep, restart it so this forced blink\n        // keeps animating on the active frame cadence immediately.\n        self.blinkTask?.cancel()\n        self.blinkTask = nil\n        self.updateBlinkingState()\n        self.tickBlink(now: now)\n    }\n\n    private func shouldAnimate(provider: UsageProvider, mergeIcons: Bool? = nil) -> Bool {\n        if self.store.debugForceAnimation { return true }\n\n        let isMerged = mergeIcons ?? self.shouldMergeIcons\n        let isVisible = isMerged ? self.isEnabled(provider) : self.isVisible(provider)\n        guard isVisible else { return false }\n\n        // Don't animate for fallback provider - it's only shown as a placeholder when nothing is enabled.\n        // Animating the fallback causes unnecessary CPU usage (battery drain). See #269, #139.\n        let isEnabled = self.isEnabled(provider)\n        let isFallbackOnly = !isEnabled && self.fallbackProvider == provider\n        if isFallbackOnly { return false }\n\n        let isStale = self.store.isStale(provider: provider)\n        let hasData = self.store.snapshot(for: provider) != nil\n        if provider == .warp, !hasData, self.store.refreshingProviders.contains(provider) {\n            return true\n        }\n        return !hasData && !isStale\n    }\n\n    func updateAnimationState() {\n        let needsAnimation = self.needsMenuBarIconAnimation()\n        if needsAnimation {\n            if self.animationDriver == nil {\n                if let forced = self.settings.debugLoadingPattern {\n                    self.animationPattern = forced\n                } else if !LoadingPattern.allCases.contains(self.animationPattern) {\n                    self.animationPattern = .knightRider\n                }\n                self.animationPhase = 0\n                let driver = DisplayLinkDriver(onTick: { [weak self] in\n                    self?.updateAnimationFrame()\n                })\n                self.animationDriver = driver\n                driver.start(fps: 60)\n            } else if let forced = self.settings.debugLoadingPattern, forced != self.animationPattern {\n                self.animationPattern = forced\n                self.animationPhase = 0\n            }\n        } else {\n            self.animationDriver?.stop()\n            self.animationDriver = nil\n            self.animationPhase = 0\n            if self.shouldMergeIcons {\n                self.applyIcon(phase: nil)\n            } else {\n                UsageProvider.allCases.forEach { self.applyIcon(for: $0, phase: nil) }\n            }\n        }\n    }\n\n    private func updateAnimationFrame() {\n        self.animationPhase += 0.045 // half-speed animation\n        if self.shouldMergeIcons {\n            self.applyIcon(phase: self.animationPhase)\n        } else {\n            UsageProvider.allCases.forEach { self.applyIcon(for: $0, phase: self.animationPhase) }\n        }\n    }\n\n    nonisolated static func shouldUseOpenRouterBrandFallback(\n        provider: UsageProvider,\n        snapshot: UsageSnapshot?) -> Bool\n    {\n        guard provider == .openrouter,\n              let openRouterUsage = snapshot?.openRouterUsage\n        else {\n            return false\n        }\n        return openRouterUsage.keyQuotaStatus == .noLimitConfigured\n    }\n\n    nonisolated static func brandImageWithStatusOverlay(\n        brand: NSImage,\n        statusIndicator: ProviderStatusIndicator) -> NSImage\n    {\n        guard statusIndicator.hasIssue else { return brand }\n\n        let image = NSImage(size: brand.size)\n        image.lockFocus()\n        brand.draw(\n            at: .zero,\n            from: NSRect(origin: .zero, size: brand.size),\n            operation: .sourceOver,\n            fraction: 1.0)\n        Self.drawBrandStatusOverlay(indicator: statusIndicator, size: brand.size)\n        image.unlockFocus()\n        image.isTemplate = brand.isTemplate\n        return image\n    }\n\n    private nonisolated static func drawBrandStatusOverlay(indicator: ProviderStatusIndicator, size: NSSize) {\n        guard indicator.hasIssue else { return }\n\n        let color = NSColor.labelColor\n        switch indicator {\n        case .minor, .maintenance:\n            let dotSize = CGSize(width: 4, height: 4)\n            let dotOrigin = CGPoint(x: size.width - dotSize.width - 2, y: 2)\n            color.setFill()\n            NSBezierPath(ovalIn: CGRect(origin: dotOrigin, size: dotSize)).fill()\n        case .major, .critical, .unknown:\n            color.setFill()\n            let lineRect = CGRect(x: size.width - 6, y: 4, width: 2, height: 6)\n            NSBezierPath(roundedRect: lineRect, xRadius: 1, yRadius: 1).fill()\n            let dotRect = CGRect(x: size.width - 6, y: 2, width: 2, height: 2)\n            NSBezierPath(ovalIn: dotRect).fill()\n        case .none:\n            break\n        }\n    }\n\n    private func advanceAnimationPattern() {\n        let patterns = LoadingPattern.allCases\n        if let idx = patterns.firstIndex(of: self.animationPattern) {\n            let next = patterns.indices.contains(idx + 1) ? patterns[idx + 1] : patterns.first\n            self.animationPattern = next ?? .knightRider\n        } else {\n            self.animationPattern = .knightRider\n        }\n    }\n\n    @objc func handleDebugReplayNotification(_ notification: Notification) {\n        if let raw = notification.userInfo?[\"pattern\"] as? String,\n           let selected = LoadingPattern(rawValue: raw)\n        {\n            self.animationPattern = selected\n        } else if let forced = self.settings.debugLoadingPattern {\n            self.animationPattern = forced\n        } else {\n            self.advanceAnimationPattern()\n        }\n        self.animationPhase = 0\n        self.updateAnimationState()\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/StatusItemController+Menu.swift",
    "content": "import AppKit\nimport CodexBarCore\nimport Observation\nimport QuartzCore\nimport SwiftUI\n\nextension ProviderSwitcherSelection {\n    fileprivate var provider: UsageProvider? {\n        switch self {\n        case .overview:\n            nil\n        case let .provider(provider):\n            provider\n        }\n    }\n}\n\nprivate struct OverviewMenuCardRowView: View {\n    let model: UsageMenuCardView.Model\n    let width: CGFloat\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 0) {\n            UsageMenuCardHeaderSectionView(\n                model: self.model,\n                showDivider: self.hasUsageBlock,\n                width: self.width)\n            if self.hasUsageBlock {\n                UsageMenuCardUsageSectionView(\n                    model: self.model,\n                    showBottomDivider: false,\n                    bottomPadding: 6,\n                    width: self.width)\n            }\n        }\n        .frame(width: self.width, alignment: .leading)\n    }\n\n    private var hasUsageBlock: Bool {\n        !self.model.metrics.isEmpty || !self.model.usageNotes.isEmpty || self.model.placeholder != nil\n    }\n}\n\n// MARK: - NSMenu construction\n\nextension StatusItemController {\n    private static let menuCardBaseWidth: CGFloat = 310\n    private static let maxOverviewProviders = SettingsStore.mergedOverviewProviderLimit\n    private static let overviewRowIdentifierPrefix = \"overviewRow-\"\n    private static let menuOpenRefreshDelay: Duration = .seconds(1.2)\n    private struct OpenAIWebMenuItems {\n        let hasUsageBreakdown: Bool\n        let hasCreditsHistory: Bool\n        let hasCostHistory: Bool\n    }\n\n    private struct TokenAccountMenuDisplay {\n        let provider: UsageProvider\n        let accounts: [ProviderTokenAccount]\n        let snapshots: [TokenAccountUsageSnapshot]\n        let activeIndex: Int\n        let showAll: Bool\n        let showSwitcher: Bool\n    }\n\n    private func menuCardWidth(for providers: [UsageProvider], menu: NSMenu? = nil) -> CGFloat {\n        _ = menu\n        return Self.menuCardBaseWidth\n    }\n\n    func makeMenu() -> NSMenu {\n        guard self.shouldMergeIcons else {\n            return self.makeMenu(for: nil)\n        }\n        let menu = NSMenu()\n        menu.autoenablesItems = false\n        menu.delegate = self\n        return menu\n    }\n\n    func menuWillOpen(_ menu: NSMenu) {\n        if self.isHostedSubviewMenu(menu) {\n            self.refreshHostedSubviewHeights(in: menu)\n            if Self.menuRefreshEnabled, self.isOpenAIWebSubviewMenu(menu) {\n                self.store.requestOpenAIDashboardRefreshIfStale(reason: \"submenu open\")\n            }\n            self.openMenus[ObjectIdentifier(menu)] = menu\n            // Removed redundant async refresh - single pass is sufficient after initial layout\n            return\n        }\n\n        var provider: UsageProvider?\n        if self.shouldMergeIcons {\n            let resolvedProvider = self.resolvedMenuProvider()\n            self.lastMenuProvider = resolvedProvider ?? .codex\n            provider = resolvedProvider\n        } else {\n            if let menuProvider = self.menuProviders[ObjectIdentifier(menu)] {\n                self.lastMenuProvider = menuProvider\n                provider = menuProvider\n            } else if menu === self.fallbackMenu {\n                self.lastMenuProvider = self.store.enabledProvidersForDisplay().first ?? .codex\n                provider = nil\n            } else {\n                let resolved = self.store.enabledProvidersForDisplay().first ?? .codex\n                self.lastMenuProvider = resolved\n                provider = resolved\n            }\n        }\n\n        let didRefresh = self.menuNeedsRefresh(menu)\n        if didRefresh {\n            self.populateMenu(menu, provider: provider)\n            self.markMenuFresh(menu)\n            // Heights are already set during populateMenu, no need to remeasure\n        }\n        self.openMenus[ObjectIdentifier(menu)] = menu\n        // Only schedule refresh after menu is registered as open - refreshNow is called async\n        if Self.menuRefreshEnabled {\n            self.scheduleOpenMenuRefresh(for: menu)\n        }\n    }\n\n    func menuDidClose(_ menu: NSMenu) {\n        let key = ObjectIdentifier(menu)\n\n        self.openMenus.removeValue(forKey: key)\n        self.menuRefreshTasks.removeValue(forKey: key)?.cancel()\n\n        let isPersistentMenu = menu === self.mergedMenu ||\n            menu === self.fallbackMenu ||\n            self.providerMenus.values.contains { $0 === menu }\n        if !isPersistentMenu {\n            self.menuProviders.removeValue(forKey: key)\n            self.menuVersions.removeValue(forKey: key)\n        }\n        for menuItem in menu.items {\n            (menuItem.view as? MenuCardHighlighting)?.setHighlighted(false)\n        }\n    }\n\n    func menu(_ menu: NSMenu, willHighlight item: NSMenuItem?) {\n        for menuItem in menu.items {\n            let highlighted = menuItem == item && menuItem.isEnabled\n            (menuItem.view as? MenuCardHighlighting)?.setHighlighted(highlighted)\n        }\n    }\n\n    private func populateMenu(_ menu: NSMenu, provider: UsageProvider?) {\n        let enabledProviders = self.store.enabledProvidersForDisplay()\n        let includesOverview = self.includesOverviewTab(enabledProviders: enabledProviders)\n        let switcherSelection = self.shouldMergeIcons && enabledProviders.count > 1\n            ? self.resolvedSwitcherSelection(\n                enabledProviders: enabledProviders,\n                includesOverview: includesOverview)\n            : nil\n        let isOverviewSelected = switcherSelection == .overview\n        let selectedProvider = if isOverviewSelected {\n            self.resolvedMenuProvider(enabledProviders: enabledProviders)\n        } else {\n            switcherSelection?.provider ?? provider\n        }\n        let menuWidth = self.menuCardWidth(for: enabledProviders, menu: menu)\n        let currentProvider = selectedProvider ?? enabledProviders.first ?? .codex\n        let tokenAccountDisplay = isOverviewSelected ? nil : self.tokenAccountMenuDisplay(for: currentProvider)\n        let showAllTokenAccounts = tokenAccountDisplay?.showAll ?? false\n        let openAIContext = self.openAIWebContext(\n            currentProvider: currentProvider,\n            showAllTokenAccounts: showAllTokenAccounts)\n\n        let hasTokenAccountSwitcher = menu.items.contains { $0.view is TokenAccountSwitcherView }\n        let switcherProvidersMatch = enabledProviders == self.lastSwitcherProviders\n        let switcherUsageBarsShowUsedMatch = self.settings.usageBarsShowUsed == self.lastSwitcherUsageBarsShowUsed\n        let switcherSelectionMatches = switcherSelection == self.lastMergedSwitcherSelection\n        let switcherOverviewAvailabilityMatches = includesOverview == self.lastSwitcherIncludesOverview\n        let canSmartUpdate = self.shouldMergeIcons &&\n            enabledProviders.count > 1 &&\n            !isOverviewSelected &&\n            switcherProvidersMatch &&\n            switcherUsageBarsShowUsedMatch &&\n            switcherSelectionMatches &&\n            switcherOverviewAvailabilityMatches &&\n            tokenAccountDisplay == nil &&\n            !hasTokenAccountSwitcher &&\n            !menu.items.isEmpty &&\n            menu.items.first?.view is ProviderSwitcherView\n\n        if canSmartUpdate {\n            self.updateMenuContent(\n                menu,\n                provider: selectedProvider,\n                currentProvider: currentProvider,\n                menuWidth: menuWidth,\n                openAIContext: openAIContext)\n            return\n        }\n\n        menu.removeAllItems()\n\n        let descriptor = MenuDescriptor.build(\n            provider: selectedProvider,\n            store: self.store,\n            settings: self.settings,\n            account: self.account,\n            updateReady: self.updater.updateStatus.isUpdateReady,\n            includeContextualActions: !isOverviewSelected)\n\n        self.addProviderSwitcherIfNeeded(\n            to: menu,\n            enabledProviders: enabledProviders,\n            includesOverview: includesOverview,\n            selection: switcherSelection ?? .provider(currentProvider))\n        // Track which providers the switcher was built with for smart update detection\n        if self.shouldMergeIcons, enabledProviders.count > 1 {\n            self.lastSwitcherProviders = enabledProviders\n            self.lastSwitcherUsageBarsShowUsed = self.settings.usageBarsShowUsed\n            self.lastMergedSwitcherSelection = switcherSelection\n            self.lastSwitcherIncludesOverview = includesOverview\n        }\n        self.addTokenAccountSwitcherIfNeeded(to: menu, display: tokenAccountDisplay)\n        let menuContext = MenuCardContext(\n            currentProvider: currentProvider,\n            selectedProvider: selectedProvider,\n            menuWidth: menuWidth,\n            tokenAccountDisplay: tokenAccountDisplay,\n            openAIContext: openAIContext)\n        if isOverviewSelected {\n            if self.addOverviewRows(\n                to: menu,\n                enabledProviders: enabledProviders,\n                menuWidth: menuWidth)\n            {\n                menu.addItem(.separator())\n            } else {\n                self.addOverviewEmptyState(to: menu, enabledProviders: enabledProviders)\n                menu.addItem(.separator())\n            }\n        } else {\n            let addedOpenAIWebItems = self.addMenuCards(to: menu, context: menuContext)\n            self.addOpenAIWebItemsIfNeeded(\n                to: menu,\n                currentProvider: currentProvider,\n                context: openAIContext,\n                addedOpenAIWebItems: addedOpenAIWebItems)\n        }\n        self.addActionableSections(descriptor.sections, to: menu, width: menuWidth)\n    }\n\n    /// Smart update: only rebuild content sections when switching providers (keep the switcher intact).\n    private func updateMenuContent(\n        _ menu: NSMenu,\n        provider: UsageProvider?,\n        currentProvider: UsageProvider,\n        menuWidth: CGFloat,\n        openAIContext: OpenAIWebContext)\n    {\n        // Batch menu updates to prevent visual flickering during provider switch.\n        CATransaction.begin()\n        CATransaction.setDisableActions(true)\n        defer { CATransaction.commit() }\n\n        var contentStartIndex = 0\n        if menu.items.first?.view is ProviderSwitcherView {\n            contentStartIndex = 2\n        }\n        if menu.items.count > contentStartIndex,\n           menu.items[contentStartIndex].view is TokenAccountSwitcherView\n        {\n            contentStartIndex += 2\n        }\n        while menu.items.count > contentStartIndex {\n            menu.removeItem(at: contentStartIndex)\n        }\n\n        let descriptor = MenuDescriptor.build(\n            provider: provider,\n            store: self.store,\n            settings: self.settings,\n            account: self.account,\n            updateReady: self.updater.updateStatus.isUpdateReady)\n\n        let menuContext = MenuCardContext(\n            currentProvider: currentProvider,\n            selectedProvider: provider,\n            menuWidth: menuWidth,\n            tokenAccountDisplay: nil,\n            openAIContext: openAIContext)\n        let addedOpenAIWebItems = self.addMenuCards(to: menu, context: menuContext)\n        self.addOpenAIWebItemsIfNeeded(\n            to: menu,\n            currentProvider: currentProvider,\n            context: openAIContext,\n            addedOpenAIWebItems: addedOpenAIWebItems)\n        self.addActionableSections(descriptor.sections, to: menu, width: menuWidth)\n    }\n\n    private struct OpenAIWebContext {\n        let hasUsageBreakdown: Bool\n        let hasCreditsHistory: Bool\n        let hasCostHistory: Bool\n        let hasOpenAIWebMenuItems: Bool\n    }\n\n    private struct MenuCardContext {\n        let currentProvider: UsageProvider\n        let selectedProvider: UsageProvider?\n        let menuWidth: CGFloat\n        let tokenAccountDisplay: TokenAccountMenuDisplay?\n        let openAIContext: OpenAIWebContext\n    }\n\n    private func openAIWebContext(\n        currentProvider: UsageProvider,\n        showAllTokenAccounts: Bool) -> OpenAIWebContext\n    {\n        let dashboard = self.store.openAIDashboard\n        let openAIWebEligible = currentProvider == .codex &&\n            self.store.openAIDashboardRequiresLogin == false &&\n            dashboard != nil\n        let hasCreditsHistory = openAIWebEligible && !(dashboard?.dailyBreakdown ?? []).isEmpty\n        let hasUsageBreakdown = openAIWebEligible && !(dashboard?.usageBreakdown ?? []).isEmpty\n        let hasCostHistory = self.settings.isCostUsageEffectivelyEnabled(for: currentProvider) &&\n            (self.store.tokenSnapshot(for: currentProvider)?.daily.isEmpty == false)\n        let hasOpenAIWebMenuItems = !showAllTokenAccounts &&\n            (hasCreditsHistory || hasUsageBreakdown || hasCostHistory)\n        return OpenAIWebContext(\n            hasUsageBreakdown: hasUsageBreakdown,\n            hasCreditsHistory: hasCreditsHistory,\n            hasCostHistory: hasCostHistory,\n            hasOpenAIWebMenuItems: hasOpenAIWebMenuItems)\n    }\n\n    private func addProviderSwitcherIfNeeded(\n        to menu: NSMenu,\n        enabledProviders: [UsageProvider],\n        includesOverview: Bool,\n        selection: ProviderSwitcherSelection)\n    {\n        guard self.shouldMergeIcons, enabledProviders.count > 1 else { return }\n        let switcherItem = self.makeProviderSwitcherItem(\n            providers: enabledProviders,\n            includesOverview: includesOverview,\n            selected: selection,\n            menu: menu)\n        menu.addItem(switcherItem)\n        menu.addItem(.separator())\n    }\n\n    private func addTokenAccountSwitcherIfNeeded(to menu: NSMenu, display: TokenAccountMenuDisplay?) {\n        guard let display, display.showSwitcher else { return }\n        let switcherItem = self.makeTokenAccountSwitcherItem(display: display, menu: menu)\n        menu.addItem(switcherItem)\n        menu.addItem(.separator())\n    }\n\n    @discardableResult\n    private func addOverviewRows(\n        to menu: NSMenu,\n        enabledProviders: [UsageProvider],\n        menuWidth: CGFloat) -> Bool\n    {\n        let overviewProviders = self.settings.reconcileMergedOverviewSelectedProviders(\n            activeProviders: enabledProviders)\n        let rows: [(provider: UsageProvider, model: UsageMenuCardView.Model)] = overviewProviders\n            .compactMap { provider in\n                guard let model = self.menuCardModel(for: provider) else { return nil }\n                return (provider: provider, model: model)\n            }\n        guard !rows.isEmpty else { return false }\n\n        for (index, row) in rows.enumerated() {\n            let identifier = \"\\(Self.overviewRowIdentifierPrefix)\\(row.provider.rawValue)\"\n            let item = self.makeMenuCardItem(\n                OverviewMenuCardRowView(model: row.model, width: menuWidth),\n                id: identifier,\n                width: menuWidth,\n                onClick: { [weak self, weak menu] in\n                    guard let self, let menu else { return }\n                    self.selectOverviewProvider(row.provider, menu: menu)\n                })\n            // Keep menu item action wired for keyboard activation and accessibility action paths.\n            item.target = self\n            item.action = #selector(self.selectOverviewProvider(_:))\n            menu.addItem(item)\n            if index < rows.count - 1 {\n                menu.addItem(.separator())\n            }\n        }\n        return true\n    }\n\n    private func addOverviewEmptyState(to menu: NSMenu, enabledProviders: [UsageProvider]) {\n        let resolvedProviders = self.settings.resolvedMergedOverviewProviders(\n            activeProviders: enabledProviders,\n            maxVisibleProviders: Self.maxOverviewProviders)\n        let message = if resolvedProviders.isEmpty {\n            \"No providers selected for Overview.\"\n        } else {\n            \"No overview data available.\"\n        }\n        let item = NSMenuItem(title: message, action: nil, keyEquivalent: \"\")\n        item.isEnabled = false\n        item.representedObject = \"overviewEmptyState\"\n        menu.addItem(item)\n    }\n\n    private func addMenuCards(to menu: NSMenu, context: MenuCardContext) -> Bool {\n        if let tokenAccountDisplay = context.tokenAccountDisplay, tokenAccountDisplay.showAll {\n            let accountSnapshots = tokenAccountDisplay.snapshots\n            let cards = accountSnapshots.isEmpty\n                ? []\n                : accountSnapshots.compactMap { accountSnapshot in\n                    self.menuCardModel(\n                        for: context.currentProvider,\n                        snapshotOverride: accountSnapshot.snapshot,\n                        errorOverride: accountSnapshot.error)\n                }\n            if cards.isEmpty, let model = self.menuCardModel(for: context.selectedProvider) {\n                menu.addItem(self.makeMenuCardItem(\n                    UsageMenuCardView(model: model, width: context.menuWidth),\n                    id: \"menuCard\",\n                    width: context.menuWidth))\n                menu.addItem(.separator())\n            } else {\n                for (index, model) in cards.enumerated() {\n                    menu.addItem(self.makeMenuCardItem(\n                        UsageMenuCardView(model: model, width: context.menuWidth),\n                        id: \"menuCard-\\(index)\",\n                        width: context.menuWidth))\n                    if index < cards.count - 1 {\n                        menu.addItem(.separator())\n                    }\n                }\n                if !cards.isEmpty {\n                    menu.addItem(.separator())\n                }\n            }\n            return false\n        }\n\n        guard let model = self.menuCardModel(for: context.selectedProvider) else { return false }\n        if context.openAIContext.hasOpenAIWebMenuItems {\n            let webItems = OpenAIWebMenuItems(\n                hasUsageBreakdown: context.openAIContext.hasUsageBreakdown,\n                hasCreditsHistory: context.openAIContext.hasCreditsHistory,\n                hasCostHistory: context.openAIContext.hasCostHistory)\n            self.addMenuCardSections(\n                to: menu,\n                model: model,\n                provider: context.currentProvider,\n                width: context.menuWidth,\n                webItems: webItems)\n            return true\n        }\n\n        menu.addItem(self.makeMenuCardItem(\n            UsageMenuCardView(model: model, width: context.menuWidth),\n            id: \"menuCard\",\n            width: context.menuWidth))\n        if context.currentProvider == .codex, model.creditsText != nil {\n            menu.addItem(self.makeBuyCreditsItem())\n        }\n        menu.addItem(.separator())\n        return false\n    }\n\n    private func addOpenAIWebItemsIfNeeded(\n        to menu: NSMenu,\n        currentProvider: UsageProvider,\n        context: OpenAIWebContext,\n        addedOpenAIWebItems: Bool)\n    {\n        guard context.hasOpenAIWebMenuItems else { return }\n        if !addedOpenAIWebItems {\n            // Only show these when we actually have additional data.\n            if context.hasUsageBreakdown {\n                _ = self.addUsageBreakdownSubmenu(to: menu)\n            }\n            if context.hasCreditsHistory {\n                _ = self.addCreditsHistorySubmenu(to: menu)\n            }\n            if context.hasCostHistory {\n                _ = self.addCostHistorySubmenu(to: menu, provider: currentProvider)\n            }\n        }\n        menu.addItem(.separator())\n    }\n\n    private func addActionableSections(_ sections: [MenuDescriptor.Section], to menu: NSMenu, width: CGFloat) {\n        let actionableSections = sections.filter { section in\n            section.entries.contains { entry in\n                if case .action = entry { return true }\n                return false\n            }\n        }\n        for (index, section) in actionableSections.enumerated() {\n            for entry in section.entries {\n                switch entry {\n                case let .text(text, style):\n                    if style == .secondary {\n                        menu.addItem(self.makeWrappedSecondaryTextItem(text: text, width: width))\n                        continue\n                    }\n                    let item = NSMenuItem(title: text, action: nil, keyEquivalent: \"\")\n                    item.isEnabled = false\n                    if style == .headline {\n                        let font = NSFont.systemFont(ofSize: NSFont.systemFontSize, weight: .semibold)\n                        item.attributedTitle = NSAttributedString(string: text, attributes: [.font: font])\n                    } else if style == .secondary {\n                        let font = NSFont.systemFont(ofSize: NSFont.smallSystemFontSize)\n                        item.attributedTitle = NSAttributedString(\n                            string: text,\n                            attributes: [.font: font, .foregroundColor: NSColor.secondaryLabelColor])\n                    }\n                    menu.addItem(item)\n                case let .action(title, action):\n                    let (selector, represented) = self.selector(for: action)\n                    let item = NSMenuItem(title: title, action: selector, keyEquivalent: \"\")\n                    item.target = self\n                    item.representedObject = represented\n                    if let iconName = action.systemImageName,\n                       let image = NSImage(systemSymbolName: iconName, accessibilityDescription: nil)\n                    {\n                        image.isTemplate = true\n                        image.size = NSSize(width: 16, height: 16)\n                        item.image = image\n                    }\n                    if case let .switchAccount(targetProvider) = action,\n                       let subtitle = self.switchAccountSubtitle(for: targetProvider)\n                    {\n                        item.isEnabled = false\n                        self.applySubtitle(subtitle, to: item, title: title)\n                    }\n                    menu.addItem(item)\n                case .divider:\n                    menu.addItem(.separator())\n                }\n            }\n            if index < actionableSections.count - 1 {\n                menu.addItem(.separator())\n            }\n        }\n    }\n\n    private func makeWrappedSecondaryTextItem(text: String, width: CGFloat) -> NSMenuItem {\n        let item = NSMenuItem(title: text, action: nil, keyEquivalent: \"\")\n        let view = self.makeWrappedSecondaryTextView(text: text)\n        let height = self.menuTextItemHeight(for: view, width: width)\n        view.frame = NSRect(origin: .zero, size: NSSize(width: width, height: height))\n        item.view = view\n        item.isEnabled = false\n        item.toolTip = text\n        return item\n    }\n\n    private func makeWrappedSecondaryTextView(text: String) -> NSView {\n        let container = NSView()\n        container.translatesAutoresizingMaskIntoConstraints = false\n\n        let textField = NSTextField(wrappingLabelWithString: text)\n        textField.font = NSFont.menuFont(ofSize: NSFont.smallSystemFontSize)\n        textField.textColor = NSColor.secondaryLabelColor\n        textField.lineBreakMode = .byWordWrapping\n        textField.maximumNumberOfLines = 0\n        textField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)\n        textField.translatesAutoresizingMaskIntoConstraints = false\n\n        container.addSubview(textField)\n        NSLayoutConstraint.activate([\n            textField.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 18),\n            textField.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -10),\n            textField.topAnchor.constraint(equalTo: container.topAnchor, constant: 2),\n            textField.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: -2),\n        ])\n\n        return container\n    }\n\n    private func menuTextItemHeight(for view: NSView, width: CGFloat) -> CGFloat {\n        view.frame = NSRect(origin: .zero, size: NSSize(width: width, height: 1))\n        view.layoutSubtreeIfNeeded()\n        return max(1, ceil(view.fittingSize.height))\n    }\n\n    func makeMenu(for provider: UsageProvider?) -> NSMenu {\n        let menu = NSMenu()\n        menu.autoenablesItems = false\n        menu.delegate = self\n        if let provider {\n            self.menuProviders[ObjectIdentifier(menu)] = provider\n        }\n        return menu\n    }\n\n    private func makeProviderSwitcherItem(\n        providers: [UsageProvider],\n        includesOverview: Bool,\n        selected: ProviderSwitcherSelection,\n        menu: NSMenu) -> NSMenuItem\n    {\n        let view = ProviderSwitcherView(\n            providers: providers,\n            selected: selected,\n            includesOverview: includesOverview,\n            width: self.menuCardWidth(for: providers, menu: menu),\n            showsIcons: self.settings.switcherShowsIcons,\n            iconProvider: { [weak self] provider in\n                self?.switcherIcon(for: provider) ?? NSImage()\n            },\n            weeklyRemainingProvider: { [weak self] provider in\n                self?.switcherWeeklyRemaining(for: provider)\n            },\n            onSelect: { [weak self, weak menu] selection in\n                guard let self, let menu else { return }\n                switch selection {\n                case .overview:\n                    self.settings.mergedMenuLastSelectedWasOverview = true\n                    self.lastMergedSwitcherSelection = .overview\n                    let provider = self.resolvedMenuProvider()\n                    self.lastMenuProvider = provider ?? .codex\n                    self.populateMenu(menu, provider: provider)\n                case let .provider(provider):\n                    self.settings.mergedMenuLastSelectedWasOverview = false\n                    self.lastMergedSwitcherSelection = .provider(provider)\n                    self.selectedMenuProvider = provider\n                    self.lastMenuProvider = provider\n                    self.populateMenu(menu, provider: provider)\n                }\n                self.markMenuFresh(menu)\n                self.applyIcon(phase: nil)\n            })\n        let item = NSMenuItem()\n        item.view = view\n        item.isEnabled = false\n        return item\n    }\n\n    private func makeTokenAccountSwitcherItem(\n        display: TokenAccountMenuDisplay,\n        menu: NSMenu) -> NSMenuItem\n    {\n        let view = TokenAccountSwitcherView(\n            accounts: display.accounts,\n            selectedIndex: display.activeIndex,\n            width: self.menuCardWidth(for: self.store.enabledProvidersForDisplay(), menu: menu),\n            onSelect: { [weak self, weak menu] index in\n                guard let self, let menu else { return }\n                self.settings.setActiveTokenAccountIndex(index, for: display.provider)\n                Task { @MainActor in\n                    await ProviderInteractionContext.$current.withValue(.userInitiated) {\n                        await self.store.refresh()\n                    }\n                }\n                self.populateMenu(menu, provider: display.provider)\n                self.markMenuFresh(menu)\n                self.applyIcon(phase: nil)\n            })\n        let item = NSMenuItem()\n        item.view = view\n        item.isEnabled = false\n        return item\n    }\n\n    private func resolvedMenuProvider(enabledProviders: [UsageProvider]? = nil) -> UsageProvider? {\n        let enabled = enabledProviders ?? self.store.enabledProvidersForDisplay()\n        if enabled.isEmpty { return .codex }\n        if let selected = self.selectedMenuProvider, enabled.contains(selected) {\n            return selected\n        }\n        // Prefer an available provider so the default menu content matches the status icon.\n        // Falls back to first display provider when all lack credentials.\n        return enabled.first(where: { self.store.isProviderAvailable($0) }) ?? enabled.first\n    }\n\n    private func includesOverviewTab(enabledProviders: [UsageProvider]) -> Bool {\n        !self.settings.resolvedMergedOverviewProviders(\n            activeProviders: enabledProviders,\n            maxVisibleProviders: Self.maxOverviewProviders).isEmpty\n    }\n\n    private func resolvedSwitcherSelection(\n        enabledProviders: [UsageProvider],\n        includesOverview: Bool) -> ProviderSwitcherSelection\n    {\n        if includesOverview, self.settings.mergedMenuLastSelectedWasOverview {\n            return .overview\n        }\n        return .provider(self.resolvedMenuProvider(enabledProviders: enabledProviders) ?? .codex)\n    }\n\n    private func tokenAccountMenuDisplay(for provider: UsageProvider) -> TokenAccountMenuDisplay? {\n        guard TokenAccountSupportCatalog.support(for: provider) != nil else { return nil }\n        let accounts = self.settings.tokenAccounts(for: provider)\n        guard accounts.count > 1 else { return nil }\n        let activeIndex = self.settings.tokenAccountsData(for: provider)?.clampedActiveIndex() ?? 0\n        let showAll = self.settings.showAllTokenAccountsInMenu\n        let snapshots = showAll ? (self.store.accountSnapshots[provider] ?? []) : []\n        return TokenAccountMenuDisplay(\n            provider: provider,\n            accounts: accounts,\n            snapshots: snapshots,\n            activeIndex: activeIndex,\n            showAll: showAll,\n            showSwitcher: !showAll)\n    }\n\n    private func menuNeedsRefresh(_ menu: NSMenu) -> Bool {\n        let key = ObjectIdentifier(menu)\n        return self.menuVersions[key] != self.menuContentVersion\n    }\n\n    private func markMenuFresh(_ menu: NSMenu) {\n        let key = ObjectIdentifier(menu)\n        self.menuVersions[key] = self.menuContentVersion\n    }\n\n    func refreshOpenMenusIfNeeded() {\n        guard !self.openMenus.isEmpty else { return }\n        for (key, menu) in self.openMenus {\n            guard key == ObjectIdentifier(menu) else {\n                // Clean up orphaned menu entries from all tracking dictionaries\n                self.openMenus.removeValue(forKey: key)\n                self.menuRefreshTasks.removeValue(forKey: key)?.cancel()\n                self.menuProviders.removeValue(forKey: key)\n                self.menuVersions.removeValue(forKey: key)\n                continue\n            }\n\n            if self.isHostedSubviewMenu(menu) {\n                self.refreshHostedSubviewHeights(in: menu)\n                continue\n            }\n\n            if self.menuNeedsRefresh(menu) {\n                let provider = self.menuProvider(for: menu)\n                self.populateMenu(menu, provider: provider)\n                self.markMenuFresh(menu)\n                // Heights are already set during populateMenu, no need to remeasure\n            }\n        }\n    }\n\n    private func menuProvider(for menu: NSMenu) -> UsageProvider? {\n        if self.shouldMergeIcons {\n            return self.resolvedMenuProvider()\n        }\n        if let provider = self.menuProviders[ObjectIdentifier(menu)] {\n            return provider\n        }\n        if menu === self.fallbackMenu {\n            return nil\n        }\n        return self.store.enabledProvidersForDisplay().first ?? .codex\n    }\n\n    private func scheduleOpenMenuRefresh(for menu: NSMenu) {\n        // Kick off a user-initiated refresh on open (non-forced) and re-check after a delay.\n        // NEVER block menu opening with network requests.\n        if !self.store.isRefreshing {\n            self.refreshStore(forceTokenUsage: false)\n        }\n        let key = ObjectIdentifier(menu)\n        self.menuRefreshTasks[key]?.cancel()\n        self.menuRefreshTasks[key] = Task { @MainActor [weak self, weak menu] in\n            guard let self, let menu else { return }\n            try? await Task.sleep(for: Self.menuOpenRefreshDelay)\n            guard !Task.isCancelled else { return }\n            guard self.openMenus[ObjectIdentifier(menu)] != nil else { return }\n            guard !self.store.isRefreshing else { return }\n            guard self.menuNeedsDelayedRefreshRetry(for: menu) else { return }\n            self.refreshStore(forceTokenUsage: false)\n        }\n    }\n\n    private func menuNeedsDelayedRefreshRetry(for menu: NSMenu) -> Bool {\n        let providersToCheck = self.delayedRefreshRetryProviders(for: menu)\n        guard !providersToCheck.isEmpty else { return false }\n        return providersToCheck.contains { provider in\n            self.store.isStale(provider: provider) || self.store.snapshot(for: provider) == nil\n        }\n    }\n\n    private func delayedRefreshRetryProviders(for menu: NSMenu) -> [UsageProvider] {\n        let enabledProviders = self.store.enabledProvidersForDisplay()\n        guard !enabledProviders.isEmpty else { return [] }\n        let includesOverview = self.includesOverviewTab(enabledProviders: enabledProviders)\n\n        if self.shouldMergeIcons,\n           enabledProviders.count > 1,\n           self.resolvedSwitcherSelection(\n               enabledProviders: enabledProviders,\n               includesOverview: includesOverview) == .overview\n        {\n            return self.settings.resolvedMergedOverviewProviders(\n                activeProviders: enabledProviders,\n                maxVisibleProviders: Self.maxOverviewProviders)\n        }\n\n        if let provider = self.menuProvider(for: menu)\n            ?? self.resolvedMenuProvider(enabledProviders: enabledProviders)\n        {\n            return [provider]\n        }\n        return enabledProviders\n    }\n\n    private func refreshMenuCardHeights(in menu: NSMenu) {\n        // Re-measure the menu card height right before display to avoid stale/incorrect sizing when content\n        // changes (e.g. dashboard error lines causing wrapping).\n        let cardItems = menu.items.filter { item in\n            (item.representedObject as? String)?.hasPrefix(\"menuCard\") == true\n        }\n        for item in cardItems {\n            guard let view = item.view else { continue }\n            let width = self.menuCardWidth(for: self.store.enabledProvidersForDisplay(), menu: menu)\n            let height = self.menuCardHeight(for: view, width: width)\n            view.frame = NSRect(\n                origin: .zero,\n                size: NSSize(width: width, height: height))\n        }\n    }\n\n    private func makeMenuCardItem(\n        _ view: some View,\n        id: String,\n        width: CGFloat,\n        submenu: NSMenu? = nil,\n        onClick: (() -> Void)? = nil) -> NSMenuItem\n    {\n        if !Self.menuCardRenderingEnabled {\n            let item = NSMenuItem()\n            item.isEnabled = true\n            item.representedObject = id\n            item.submenu = submenu\n            if submenu != nil {\n                item.target = self\n                item.action = #selector(self.menuCardNoOp(_:))\n            }\n            return item\n        }\n\n        let highlightState = MenuCardHighlightState()\n        let wrapped = MenuCardSectionContainerView(\n            highlightState: highlightState,\n            showsSubmenuIndicator: submenu != nil)\n        {\n            view\n        }\n        let hosting = MenuCardItemHostingView(rootView: wrapped, highlightState: highlightState, onClick: onClick)\n        // Set frame with target width immediately\n        let height = self.menuCardHeight(for: hosting, width: width)\n        hosting.frame = NSRect(origin: .zero, size: NSSize(width: width, height: height))\n        let item = NSMenuItem()\n        item.view = hosting\n        item.isEnabled = true\n        item.representedObject = id\n        item.submenu = submenu\n        if submenu != nil {\n            item.target = self\n            item.action = #selector(self.menuCardNoOp(_:))\n        }\n        return item\n    }\n\n    private func menuCardHeight(for view: NSView, width: CGFloat) -> CGFloat {\n        let basePadding: CGFloat = 6\n        let descenderSafety: CGFloat = 1\n\n        // Fast path: use protocol-based measurement when available (avoids layout passes)\n        if let measured = view as? MenuCardMeasuring {\n            return max(1, ceil(measured.measuredHeight(width: width) + basePadding + descenderSafety))\n        }\n\n        // Set frame with target width before measuring.\n        view.frame = NSRect(origin: .zero, size: NSSize(width: width, height: 1))\n\n        // Use fittingSize directly - SwiftUI hosting views respect the frame width for wrapping\n        let fitted = view.fittingSize\n\n        return max(1, ceil(fitted.height + basePadding + descenderSafety))\n    }\n\n    private func addMenuCardSections(\n        to menu: NSMenu,\n        model: UsageMenuCardView.Model,\n        provider: UsageProvider,\n        width: CGFloat,\n        webItems: OpenAIWebMenuItems)\n    {\n        let hasUsageBlock = !model.metrics.isEmpty || model.placeholder != nil\n        let hasCredits = model.creditsText != nil\n        let hasExtraUsage = model.providerCost != nil\n        let hasCost = model.tokenUsage != nil\n        let bottomPadding = CGFloat(hasCredits ? 4 : 6)\n        let sectionSpacing = CGFloat(6)\n        let usageBottomPadding = bottomPadding\n        let creditsBottomPadding = bottomPadding\n\n        let headerView = UsageMenuCardHeaderSectionView(\n            model: model,\n            showDivider: hasUsageBlock,\n            width: width)\n        menu.addItem(self.makeMenuCardItem(headerView, id: \"menuCardHeader\", width: width))\n\n        if hasUsageBlock {\n            let usageView = UsageMenuCardUsageSectionView(\n                model: model,\n                showBottomDivider: false,\n                bottomPadding: usageBottomPadding,\n                width: width)\n            let usageSubmenu = self.makeUsageSubmenu(\n                provider: provider,\n                snapshot: self.store.snapshot(for: provider),\n                webItems: webItems)\n            menu.addItem(self.makeMenuCardItem(\n                usageView,\n                id: \"menuCardUsage\",\n                width: width,\n                submenu: usageSubmenu))\n        }\n\n        if hasCredits || hasExtraUsage || hasCost {\n            menu.addItem(.separator())\n        }\n\n        if hasCredits {\n            if hasExtraUsage || hasCost {\n                menu.addItem(.separator())\n            }\n            let creditsView = UsageMenuCardCreditsSectionView(\n                model: model,\n                showBottomDivider: false,\n                topPadding: sectionSpacing,\n                bottomPadding: creditsBottomPadding,\n                width: width)\n            let creditsSubmenu = webItems.hasCreditsHistory ? self.makeCreditsHistorySubmenu() : nil\n            menu.addItem(self.makeMenuCardItem(\n                creditsView,\n                id: \"menuCardCredits\",\n                width: width,\n                submenu: creditsSubmenu))\n            if provider == .codex {\n                menu.addItem(self.makeBuyCreditsItem())\n            }\n        }\n        if hasExtraUsage {\n            if hasCredits {\n                menu.addItem(.separator())\n            }\n            let extraUsageView = UsageMenuCardExtraUsageSectionView(\n                model: model,\n                topPadding: sectionSpacing,\n                bottomPadding: bottomPadding,\n                width: width)\n            menu.addItem(self.makeMenuCardItem(\n                extraUsageView,\n                id: \"menuCardExtraUsage\",\n                width: width))\n        }\n        if hasCost {\n            if hasCredits || hasExtraUsage {\n                menu.addItem(.separator())\n            }\n            let costView = UsageMenuCardCostSectionView(\n                model: model,\n                topPadding: sectionSpacing,\n                bottomPadding: bottomPadding,\n                width: width)\n            let costSubmenu = webItems.hasCostHistory ? self.makeCostHistorySubmenu(provider: provider) : nil\n            menu.addItem(self.makeMenuCardItem(\n                costView,\n                id: \"menuCardCost\",\n                width: width,\n                submenu: costSubmenu))\n        }\n    }\n\n    private func switcherIcon(for provider: UsageProvider) -> NSImage {\n        if let brand = ProviderBrandIcon.image(for: provider) {\n            return brand\n        }\n\n        // Fallback to the dynamic icon renderer if resources are missing (e.g. dev bundle mismatch).\n        let snapshot = self.store.snapshot(for: provider)\n        let showUsed = self.settings.usageBarsShowUsed\n        let primary = showUsed ? snapshot?.primary?.usedPercent : snapshot?.primary?.remainingPercent\n        var weekly = showUsed ? snapshot?.secondary?.usedPercent : snapshot?.secondary?.remainingPercent\n        if showUsed,\n           provider == .warp,\n           let remaining = snapshot?.secondary?.remainingPercent,\n           remaining <= 0\n        {\n            // Preserve Warp \"no bonus/exhausted bonus\" layout even in show-used mode.\n            weekly = 0\n        }\n        if showUsed,\n           provider == .warp,\n           let remaining = snapshot?.secondary?.remainingPercent,\n           remaining > 0,\n           weekly == 0\n        {\n            // In show-used mode, `0` means \"unused\", not \"missing\". Keep the weekly lane present.\n            weekly = 0.0001\n        }\n        let credits = provider == .codex ? self.store.credits?.remaining : nil\n        let stale = self.store.isStale(provider: provider)\n        let style = self.store.style(for: provider)\n        let indicator = self.store.statusIndicator(for: provider)\n        let image = IconRenderer.makeIcon(\n            primaryRemaining: primary,\n            weeklyRemaining: weekly,\n            creditsRemaining: credits,\n            stale: stale,\n            style: style,\n            blink: 0,\n            wiggle: 0,\n            tilt: 0,\n            statusIndicator: indicator)\n        image.isTemplate = true\n        return image\n    }\n\n    nonisolated static func switcherWeeklyMetricPercent(\n        for provider: UsageProvider,\n        snapshot: UsageSnapshot?,\n        showUsed: Bool) -> Double?\n    {\n        let window = snapshot?.switcherWeeklyWindow(for: provider, showUsed: showUsed)\n        guard let window else { return nil }\n        return showUsed ? window.usedPercent : window.remainingPercent\n    }\n\n    private func switcherWeeklyRemaining(for provider: UsageProvider) -> Double? {\n        Self.switcherWeeklyMetricPercent(\n            for: provider,\n            snapshot: self.store.snapshot(for: provider),\n            showUsed: self.settings.usageBarsShowUsed)\n    }\n\n    private func selector(for action: MenuDescriptor.MenuAction) -> (Selector, Any?) {\n        switch action {\n        case .installUpdate: (#selector(self.installUpdate), nil)\n        case .refresh: (#selector(self.refreshNow), nil)\n        case .refreshAugmentSession: (#selector(self.refreshAugmentSession), nil)\n        case .dashboard: (#selector(self.openDashboard), nil)\n        case .statusPage: (#selector(self.openStatusPage), nil)\n        case let .switchAccount(provider): (#selector(self.runSwitchAccount(_:)), provider.rawValue)\n        case let .openTerminal(command): (#selector(self.openTerminalCommand(_:)), command)\n        case let .loginToProvider(url): (#selector(self.openLoginToProvider(_:)), url)\n        case .settings: (#selector(self.showSettingsGeneral), nil)\n        case .about: (#selector(self.showSettingsAbout), nil)\n        case .quit: (#selector(self.quit), nil)\n        case let .copyError(message): (#selector(self.copyError(_:)), message)\n        }\n    }\n\n    @MainActor\n    private protocol MenuCardHighlighting: AnyObject {\n        func setHighlighted(_ highlighted: Bool)\n    }\n\n    @MainActor\n    private protocol MenuCardMeasuring: AnyObject {\n        func measuredHeight(width: CGFloat) -> CGFloat\n    }\n\n    @MainActor\n    @Observable\n    fileprivate final class MenuCardHighlightState {\n        var isHighlighted = false\n    }\n\n    private final class MenuHostingView<Content: View>: NSHostingView<Content> {\n        override var allowsVibrancy: Bool {\n            true\n        }\n    }\n\n    @MainActor\n    private final class MenuCardItemHostingView<Content: View>: NSHostingView<Content>, MenuCardHighlighting,\n    MenuCardMeasuring {\n        private let highlightState: MenuCardHighlightState\n        private let onClick: (() -> Void)?\n        override var allowsVibrancy: Bool {\n            true\n        }\n\n        override var intrinsicContentSize: NSSize {\n            let size = super.intrinsicContentSize\n            guard self.frame.width > 0 else { return size }\n            return NSSize(width: self.frame.width, height: size.height)\n        }\n\n        init(rootView: Content, highlightState: MenuCardHighlightState, onClick: (() -> Void)? = nil) {\n            self.highlightState = highlightState\n            self.onClick = onClick\n            super.init(rootView: rootView)\n            if onClick != nil {\n                let recognizer = NSClickGestureRecognizer(target: self, action: #selector(self.handlePrimaryClick(_:)))\n                recognizer.buttonMask = 0x1\n                self.addGestureRecognizer(recognizer)\n            }\n        }\n\n        required init(rootView: Content) {\n            self.highlightState = MenuCardHighlightState()\n            self.onClick = nil\n            super.init(rootView: rootView)\n        }\n\n        @available(*, unavailable)\n        required init?(coder: NSCoder) {\n            fatalError(\"init(coder:) has not been implemented\")\n        }\n\n        override func acceptsFirstMouse(for event: NSEvent?) -> Bool {\n            true\n        }\n\n        @objc private func handlePrimaryClick(_ recognizer: NSClickGestureRecognizer) {\n            guard recognizer.state == .ended else { return }\n            self.onClick?()\n        }\n\n        func measuredHeight(width: CGFloat) -> CGFloat {\n            let controller = NSHostingController(rootView: self.rootView)\n            let measured = controller.sizeThatFits(in: CGSize(width: width, height: .greatestFiniteMagnitude))\n            return measured.height\n        }\n\n        func setHighlighted(_ highlighted: Bool) {\n            guard self.highlightState.isHighlighted != highlighted else { return }\n            self.highlightState.isHighlighted = highlighted\n        }\n    }\n\n    private struct MenuCardSectionContainerView<Content: View>: View {\n        @Bindable var highlightState: MenuCardHighlightState\n        let showsSubmenuIndicator: Bool\n        let content: Content\n\n        init(\n            highlightState: MenuCardHighlightState,\n            showsSubmenuIndicator: Bool,\n            @ViewBuilder content: () -> Content)\n        {\n            self.highlightState = highlightState\n            self.showsSubmenuIndicator = showsSubmenuIndicator\n            self.content = content()\n        }\n\n        var body: some View {\n            self.content\n                .environment(\\.menuItemHighlighted, self.highlightState.isHighlighted)\n                .foregroundStyle(MenuHighlightStyle.primary(self.highlightState.isHighlighted))\n                .background(alignment: .topLeading) {\n                    if self.highlightState.isHighlighted {\n                        RoundedRectangle(cornerRadius: 6, style: .continuous)\n                            .fill(MenuHighlightStyle.selectionBackground(true))\n                            .padding(.horizontal, 6)\n                            .padding(.vertical, 2)\n                    }\n                }\n                .overlay(alignment: .topTrailing) {\n                    if self.showsSubmenuIndicator {\n                        Image(systemName: \"chevron.right\")\n                            .font(.caption2.weight(.semibold))\n                            .foregroundStyle(MenuHighlightStyle.secondary(self.highlightState.isHighlighted))\n                            .padding(.top, 8)\n                            .padding(.trailing, 10)\n                    }\n                }\n        }\n    }\n\n    private func makeBuyCreditsItem() -> NSMenuItem {\n        let item = NSMenuItem(title: \"Buy Credits...\", action: #selector(self.openCreditsPurchase), keyEquivalent: \"\")\n        item.target = self\n        if let image = NSImage(systemSymbolName: \"plus.circle\", accessibilityDescription: nil) {\n            image.isTemplate = true\n            image.size = NSSize(width: 16, height: 16)\n            item.image = image\n        }\n        return item\n    }\n\n    @discardableResult\n    private func addCreditsHistorySubmenu(to menu: NSMenu) -> Bool {\n        guard let submenu = self.makeCreditsHistorySubmenu() else { return false }\n        let item = NSMenuItem(title: \"Credits history\", action: nil, keyEquivalent: \"\")\n        item.isEnabled = true\n        item.submenu = submenu\n        menu.addItem(item)\n        return true\n    }\n\n    @discardableResult\n    private func addUsageBreakdownSubmenu(to menu: NSMenu) -> Bool {\n        guard let submenu = self.makeUsageBreakdownSubmenu() else { return false }\n        let item = NSMenuItem(title: \"Usage breakdown\", action: nil, keyEquivalent: \"\")\n        item.isEnabled = true\n        item.submenu = submenu\n        menu.addItem(item)\n        return true\n    }\n\n    @discardableResult\n    private func addCostHistorySubmenu(to menu: NSMenu, provider: UsageProvider) -> Bool {\n        guard let submenu = self.makeCostHistorySubmenu(provider: provider) else { return false }\n        let item = NSMenuItem(title: \"Usage history (30 days)\", action: nil, keyEquivalent: \"\")\n        item.isEnabled = true\n        item.submenu = submenu\n        menu.addItem(item)\n        return true\n    }\n\n    private func makeUsageSubmenu(\n        provider: UsageProvider,\n        snapshot: UsageSnapshot?,\n        webItems: OpenAIWebMenuItems) -> NSMenu?\n    {\n        if provider == .codex, webItems.hasUsageBreakdown {\n            return self.makeUsageBreakdownSubmenu()\n        }\n        if provider == .zai {\n            return self.makeZaiUsageDetailsSubmenu(snapshot: snapshot)\n        }\n        return nil\n    }\n\n    private func makeZaiUsageDetailsSubmenu(snapshot: UsageSnapshot?) -> NSMenu? {\n        guard let timeLimit = snapshot?.zaiUsage?.timeLimit else { return nil }\n        guard !timeLimit.usageDetails.isEmpty else { return nil }\n\n        let submenu = NSMenu()\n        submenu.delegate = self\n        let titleItem = NSMenuItem(title: \"MCP details\", action: nil, keyEquivalent: \"\")\n        titleItem.isEnabled = false\n        submenu.addItem(titleItem)\n\n        if let window = timeLimit.windowLabel {\n            let item = NSMenuItem(title: \"Window: \\(window)\", action: nil, keyEquivalent: \"\")\n            item.isEnabled = false\n            submenu.addItem(item)\n        }\n        if let resetTime = timeLimit.nextResetTime {\n            let reset = self.settings.resetTimeDisplayStyle == .absolute\n                ? UsageFormatter.resetDescription(from: resetTime)\n                : UsageFormatter.resetCountdownDescription(from: resetTime)\n            let item = NSMenuItem(title: \"Resets: \\(reset)\", action: nil, keyEquivalent: \"\")\n            item.isEnabled = false\n            submenu.addItem(item)\n        }\n        submenu.addItem(.separator())\n\n        let sortedDetails = timeLimit.usageDetails.sorted {\n            $0.modelCode.localizedCaseInsensitiveCompare($1.modelCode) == .orderedAscending\n        }\n        for detail in sortedDetails {\n            let usage = UsageFormatter.tokenCountString(detail.usage)\n            let item = NSMenuItem(title: \"\\(detail.modelCode): \\(usage)\", action: nil, keyEquivalent: \"\")\n            submenu.addItem(item)\n        }\n        return submenu\n    }\n\n    private func makeUsageBreakdownSubmenu() -> NSMenu? {\n        let breakdown = self.store.openAIDashboard?.usageBreakdown ?? []\n        let width = Self.menuCardBaseWidth\n        guard !breakdown.isEmpty else { return nil }\n\n        if !Self.menuCardRenderingEnabled {\n            let submenu = NSMenu()\n            submenu.delegate = self\n            let chartItem = NSMenuItem()\n            chartItem.isEnabled = false\n            chartItem.representedObject = \"usageBreakdownChart\"\n            submenu.addItem(chartItem)\n            return submenu\n        }\n\n        let submenu = NSMenu()\n        submenu.delegate = self\n        let chartView = UsageBreakdownChartMenuView(breakdown: breakdown, width: width)\n        let hosting = MenuHostingView(rootView: chartView)\n        // Use NSHostingController for efficient size calculation without multiple layout passes\n        let controller = NSHostingController(rootView: chartView)\n        let size = controller.sizeThatFits(in: CGSize(width: width, height: .greatestFiniteMagnitude))\n        hosting.frame = NSRect(origin: .zero, size: NSSize(width: width, height: size.height))\n\n        let chartItem = NSMenuItem()\n        chartItem.view = hosting\n        chartItem.isEnabled = false\n        chartItem.representedObject = \"usageBreakdownChart\"\n        submenu.addItem(chartItem)\n        return submenu\n    }\n\n    private func makeCreditsHistorySubmenu() -> NSMenu? {\n        let breakdown = self.store.openAIDashboard?.dailyBreakdown ?? []\n        let width = Self.menuCardBaseWidth\n        guard !breakdown.isEmpty else { return nil }\n\n        if !Self.menuCardRenderingEnabled {\n            let submenu = NSMenu()\n            submenu.delegate = self\n            let chartItem = NSMenuItem()\n            chartItem.isEnabled = false\n            chartItem.representedObject = \"creditsHistoryChart\"\n            submenu.addItem(chartItem)\n            return submenu\n        }\n\n        let submenu = NSMenu()\n        submenu.delegate = self\n        let chartView = CreditsHistoryChartMenuView(breakdown: breakdown, width: width)\n        let hosting = MenuHostingView(rootView: chartView)\n        // Use NSHostingController for efficient size calculation without multiple layout passes\n        let controller = NSHostingController(rootView: chartView)\n        let size = controller.sizeThatFits(in: CGSize(width: width, height: .greatestFiniteMagnitude))\n        hosting.frame = NSRect(origin: .zero, size: NSSize(width: width, height: size.height))\n\n        let chartItem = NSMenuItem()\n        chartItem.view = hosting\n        chartItem.isEnabled = false\n        chartItem.representedObject = \"creditsHistoryChart\"\n        submenu.addItem(chartItem)\n        return submenu\n    }\n\n    private func makeCostHistorySubmenu(provider: UsageProvider) -> NSMenu? {\n        guard provider == .codex || provider == .claude || provider == .vertexai else { return nil }\n        let width = Self.menuCardBaseWidth\n        guard let tokenSnapshot = self.store.tokenSnapshot(for: provider) else { return nil }\n        guard !tokenSnapshot.daily.isEmpty else { return nil }\n\n        if !Self.menuCardRenderingEnabled {\n            let submenu = NSMenu()\n            submenu.delegate = self\n            let chartItem = NSMenuItem()\n            chartItem.isEnabled = false\n            chartItem.representedObject = \"costHistoryChart\"\n            submenu.addItem(chartItem)\n            return submenu\n        }\n\n        let submenu = NSMenu()\n        submenu.delegate = self\n        let chartView = CostHistoryChartMenuView(\n            provider: provider,\n            daily: tokenSnapshot.daily,\n            totalCostUSD: tokenSnapshot.last30DaysCostUSD,\n            width: width)\n        let hosting = MenuHostingView(rootView: chartView)\n        // Use NSHostingController for efficient size calculation without multiple layout passes\n        let controller = NSHostingController(rootView: chartView)\n        let size = controller.sizeThatFits(in: CGSize(width: width, height: .greatestFiniteMagnitude))\n        hosting.frame = NSRect(origin: .zero, size: NSSize(width: width, height: size.height))\n\n        let chartItem = NSMenuItem()\n        chartItem.view = hosting\n        chartItem.isEnabled = false\n        chartItem.representedObject = \"costHistoryChart\"\n        submenu.addItem(chartItem)\n        return submenu\n    }\n\n    private func isHostedSubviewMenu(_ menu: NSMenu) -> Bool {\n        let ids: Set = [\n            \"usageBreakdownChart\",\n            \"creditsHistoryChart\",\n            \"costHistoryChart\",\n        ]\n        return menu.items.contains { item in\n            guard let id = item.representedObject as? String else { return false }\n            return ids.contains(id)\n        }\n    }\n\n    private func isOpenAIWebSubviewMenu(_ menu: NSMenu) -> Bool {\n        let ids: Set = [\n            \"usageBreakdownChart\",\n            \"creditsHistoryChart\",\n        ]\n        return menu.items.contains { item in\n            guard let id = item.representedObject as? String else { return false }\n            return ids.contains(id)\n        }\n    }\n\n    private func refreshHostedSubviewHeights(in menu: NSMenu) {\n        let enabledProviders = self.store.enabledProvidersForDisplay()\n        let width = self.menuCardWidth(for: enabledProviders, menu: menu)\n\n        for item in menu.items {\n            guard let view = item.view else { continue }\n            view.frame = NSRect(origin: .zero, size: NSSize(width: width, height: 1))\n            view.layoutSubtreeIfNeeded()\n            let height = view.fittingSize.height\n            view.frame = NSRect(origin: .zero, size: NSSize(width: width, height: height))\n        }\n    }\n\n    private func menuCardModel(\n        for provider: UsageProvider?,\n        snapshotOverride: UsageSnapshot? = nil,\n        errorOverride: String? = nil) -> UsageMenuCardView.Model?\n    {\n        let target = provider ?? self.store.enabledProvidersForDisplay().first ?? .codex\n        let metadata = self.store.metadata(for: target)\n\n        let snapshot = snapshotOverride ?? self.store.snapshot(for: target)\n        let credits: CreditsSnapshot?\n        let creditsError: String?\n        let dashboard: OpenAIDashboardSnapshot?\n        let dashboardError: String?\n        let tokenSnapshot: CostUsageTokenSnapshot?\n        let tokenError: String?\n        if target == .codex, snapshotOverride == nil {\n            credits = self.store.credits\n            creditsError = self.store.lastCreditsError\n            dashboard = self.store.openAIDashboardRequiresLogin ? nil : self.store.openAIDashboard\n            dashboardError = self.store.lastOpenAIDashboardError\n            tokenSnapshot = self.store.tokenSnapshot(for: target)\n            tokenError = self.store.tokenError(for: target)\n        } else if target == .claude || target == .vertexai, snapshotOverride == nil {\n            credits = nil\n            creditsError = nil\n            dashboard = nil\n            dashboardError = nil\n            tokenSnapshot = self.store.tokenSnapshot(for: target)\n            tokenError = self.store.tokenError(for: target)\n        } else {\n            credits = nil\n            creditsError = nil\n            dashboard = nil\n            dashboardError = nil\n            tokenSnapshot = nil\n            tokenError = nil\n        }\n\n        let sourceLabel = snapshotOverride == nil ? self.store.sourceLabel(for: target) : nil\n        let kiloAutoMode = target == .kilo && self.settings.kiloUsageDataSource == .auto\n        let now = Date()\n        let weeklyPace = snapshot?.secondary.flatMap { window in\n            self.store.weeklyPace(provider: target, window: window, now: now)\n        }\n        let input = UsageMenuCardView.Model.Input(\n            provider: target,\n            metadata: metadata,\n            snapshot: snapshot,\n            credits: credits,\n            creditsError: creditsError,\n            dashboard: dashboard,\n            dashboardError: dashboardError,\n            tokenSnapshot: tokenSnapshot,\n            tokenError: tokenError,\n            account: self.account,\n            isRefreshing: self.store.isRefreshing,\n            lastError: errorOverride ?? self.store.error(for: target),\n            usageBarsShowUsed: self.settings.usageBarsShowUsed,\n            resetTimeDisplayStyle: self.settings.resetTimeDisplayStyle,\n            tokenCostUsageEnabled: self.settings.isCostUsageEffectivelyEnabled(for: target),\n            showOptionalCreditsAndExtraUsage: self.settings.showOptionalCreditsAndExtraUsage,\n            sourceLabel: sourceLabel,\n            kiloAutoMode: kiloAutoMode,\n            hidePersonalInfo: self.settings.hidePersonalInfo,\n            weeklyPace: weeklyPace,\n            now: now)\n        return UsageMenuCardView.Model.make(input)\n    }\n\n    @objc private func menuCardNoOp(_ sender: NSMenuItem) {\n        _ = sender\n    }\n\n    @objc private func selectOverviewProvider(_ sender: NSMenuItem) {\n        guard let represented = sender.representedObject as? String,\n              represented.hasPrefix(Self.overviewRowIdentifierPrefix)\n        else {\n            return\n        }\n        let rawProvider = String(represented.dropFirst(Self.overviewRowIdentifierPrefix.count))\n        guard let provider = UsageProvider(rawValue: rawProvider),\n              let menu = sender.menu\n        else {\n            return\n        }\n\n        self.selectOverviewProvider(provider, menu: menu)\n    }\n\n    private func selectOverviewProvider(_ provider: UsageProvider, menu: NSMenu) {\n        if !self.settings.mergedMenuLastSelectedWasOverview, self.selectedMenuProvider == provider { return }\n        self.settings.mergedMenuLastSelectedWasOverview = false\n        self.lastMergedSwitcherSelection = nil\n        self.selectedMenuProvider = provider\n        self.lastMenuProvider = provider\n        self.populateMenu(menu, provider: provider)\n        self.markMenuFresh(menu)\n        self.applyIcon(phase: nil)\n    }\n\n    private func applySubtitle(_ subtitle: String, to item: NSMenuItem, title: String) {\n        if #available(macOS 14.4, *) {\n            // NSMenuItem.subtitle is only available on macOS 14.4+.\n            item.subtitle = subtitle\n        } else {\n            item.view = self.makeMenuSubtitleView(title: title, subtitle: subtitle, isEnabled: item.isEnabled)\n            item.toolTip = \"\\(title) — \\(subtitle)\"\n        }\n    }\n\n    private func makeMenuSubtitleView(title: String, subtitle: String, isEnabled: Bool) -> NSView {\n        let container = NSView()\n        container.translatesAutoresizingMaskIntoConstraints = false\n        container.alphaValue = isEnabled ? 1.0 : 0.7\n\n        let titleField = NSTextField(labelWithString: title)\n        titleField.font = NSFont.menuFont(ofSize: NSFont.systemFontSize)\n        titleField.textColor = NSColor.labelColor\n        titleField.lineBreakMode = .byTruncatingTail\n        titleField.maximumNumberOfLines = 1\n        titleField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)\n\n        let subtitleField = NSTextField(labelWithString: subtitle)\n        subtitleField.font = NSFont.menuFont(ofSize: NSFont.smallSystemFontSize)\n        subtitleField.textColor = NSColor.secondaryLabelColor\n        subtitleField.lineBreakMode = .byTruncatingTail\n        subtitleField.maximumNumberOfLines = 1\n        subtitleField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)\n\n        let stack = NSStackView(views: [titleField, subtitleField])\n        stack.orientation = .vertical\n        stack.alignment = .leading\n        stack.spacing = 1\n        stack.translatesAutoresizingMaskIntoConstraints = false\n        container.addSubview(stack)\n\n        NSLayoutConstraint.activate([\n            stack.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 18),\n            stack.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -10),\n            stack.topAnchor.constraint(equalTo: container.topAnchor, constant: 2),\n            stack.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: -2),\n        ])\n\n        return container\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/StatusItemController+SwitcherViews.swift",
    "content": "import AppKit\nimport CodexBarCore\n\nenum ProviderSwitcherSelection: Equatable {\n    case overview\n    case provider(UsageProvider)\n}\n\nfinal class ProviderSwitcherView: NSView {\n    private struct Segment {\n        let selection: ProviderSwitcherSelection\n        let image: NSImage\n        let title: String\n    }\n\n    private struct WeeklyIndicator {\n        let track: NSView\n        let fill: NSView\n    }\n\n    private let segments: [Segment]\n    private let onSelect: (ProviderSwitcherSelection) -> Void\n    private let showsIcons: Bool\n    private let weeklyRemainingProvider: (UsageProvider) -> Double?\n    private var buttons: [NSButton] = []\n    private var weeklyIndicators: [ObjectIdentifier: WeeklyIndicator] = [:]\n    private var hoverTrackingArea: NSTrackingArea?\n    private var segmentWidths: [CGFloat] = []\n    private let selectedBackground = NSColor.controlAccentColor.cgColor\n    private let unselectedBackground = NSColor.clear.cgColor\n    private let selectedTextColor = NSColor.white\n    private let unselectedTextColor = NSColor.secondaryLabelColor\n    private let stackedIcons: Bool\n    private let rowCount: Int\n    private let rowSpacing: CGFloat\n    private let rowHeight: CGFloat\n    private var preferredWidth: CGFloat = 0\n    private var hoveredButtonTag: Int?\n    private let lightModeOverlayLayer = CALayer()\n\n    init(\n        providers: [UsageProvider],\n        selected: ProviderSwitcherSelection?,\n        includesOverview: Bool,\n        width: CGFloat,\n        showsIcons: Bool,\n        iconProvider: (UsageProvider) -> NSImage,\n        weeklyRemainingProvider: @escaping (UsageProvider) -> Double?,\n        onSelect: @escaping (ProviderSwitcherSelection) -> Void)\n    {\n        let minimumGap: CGFloat = 1\n        var segments = providers.map { provider in\n            let fullTitle = Self.switcherTitle(for: provider)\n            let icon = iconProvider(provider)\n            icon.isTemplate = true\n            // Avoid any resampling: we ship exact 16pt/32px assets for crisp rendering.\n            icon.size = NSSize(width: 16, height: 16)\n            return Segment(\n                selection: .provider(provider),\n                image: icon,\n                title: fullTitle)\n        }\n        if includesOverview {\n            let overviewIcon = Self.overviewIcon()\n            overviewIcon.isTemplate = true\n            overviewIcon.size = NSSize(width: 16, height: 16)\n            segments.insert(\n                Segment(\n                    selection: .overview,\n                    image: overviewIcon,\n                    title: \"Overview\"),\n                at: 0)\n        }\n        self.segments = segments\n        self.onSelect = onSelect\n        self.showsIcons = showsIcons\n        self.weeklyRemainingProvider = weeklyRemainingProvider\n        self.stackedIcons = showsIcons && self.segments.count > 3\n        let initialOuterPadding = Self.switcherOuterPadding(\n            for: width,\n            count: self.segments.count,\n            minimumGap: minimumGap)\n        let initialMaxAllowedSegmentWidth = Self.maxAllowedUniformSegmentWidth(\n            for: width,\n            count: self.segments.count,\n            outerPadding: initialOuterPadding,\n            minimumGap: minimumGap)\n        self.rowCount = Self.switcherRowCount(\n            width: width,\n            count: self.segments.count,\n            maxAllowedSegmentWidth: initialMaxAllowedSegmentWidth,\n            stackedIcons: self.stackedIcons)\n        self.rowSpacing = self.stackedIcons ? 4 : 2\n        if self.stackedIcons && self.rowCount >= 3 {\n            self.rowHeight = 40\n        } else {\n            self.rowHeight = self.stackedIcons ? 36 : 30\n        }\n        let height: CGFloat = self.rowHeight * CGFloat(self.rowCount)\n            + self.rowSpacing * CGFloat(max(0, self.rowCount - 1))\n        self.preferredWidth = width\n        super.init(frame: NSRect(x: 0, y: 0, width: width, height: height))\n        Self.clearButtonWidthCache()\n        self.wantsLayer = true\n        self.layer?.masksToBounds = false\n        self.lightModeOverlayLayer.masksToBounds = false\n        self.layer?.insertSublayer(self.lightModeOverlayLayer, at: 0)\n        self.updateLightModeStyling()\n\n        let layoutCount = Self.layoutCount(for: self.segments.count, rows: self.rowCount)\n        let outerPadding: CGFloat = Self.switcherOuterPadding(\n            for: width,\n            count: layoutCount,\n            minimumGap: minimumGap)\n        let maxAllowedSegmentWidth = Self.maxAllowedUniformSegmentWidth(\n            for: width,\n            count: layoutCount,\n            outerPadding: outerPadding,\n            minimumGap: minimumGap)\n\n        func makeButton(index: Int, segment: Segment) -> NSButton {\n            let button: NSButton\n            if self.stackedIcons {\n                let stacked = StackedToggleButton(\n                    title: segment.title,\n                    image: segment.image,\n                    target: self,\n                    action: #selector(self.handleSelection(_:)))\n                stacked.setAllowsTwoLineTitle(self.rowCount >= 3)\n                if self.rowCount >= 4 {\n                    stacked.setTitleFontSize(NSFont.smallSystemFontSize - 3)\n                }\n                button = stacked\n            } else if self.showsIcons {\n                let inline = InlineIconToggleButton(\n                    title: segment.title,\n                    image: segment.image,\n                    target: self,\n                    action: #selector(self.handleSelection(_:)))\n                button = inline\n            } else {\n                button = PaddedToggleButton(\n                    title: segment.title,\n                    target: self,\n                    action: #selector(self.handleSelection(_:)))\n            }\n            button.tag = index\n            if self.showsIcons {\n                if self.stackedIcons {\n                    // StackedToggleButton manages its own image view.\n                } else {\n                    // InlineIconToggleButton manages its own image view.\n                }\n            } else {\n                button.image = nil\n                button.imagePosition = .noImage\n            }\n\n            let remaining: Double? = switch segment.selection {\n            case let .provider(provider):\n                self.weeklyRemainingProvider(provider)\n            case .overview:\n                nil\n            }\n            self.addWeeklyIndicator(to: button, selection: segment.selection, remainingPercent: remaining)\n            button.bezelStyle = .regularSquare\n            button.isBordered = false\n            button.controlSize = .small\n            button.font = NSFont.systemFont(ofSize: NSFont.smallSystemFontSize)\n            button.setButtonType(.toggle)\n            button.contentTintColor = self.unselectedTextColor\n            button.alignment = .center\n            button.wantsLayer = true\n            button.layer?.cornerRadius = 6\n            button.state = (selected == segment.selection) ? .on : .off\n            button.toolTip = nil\n            button.translatesAutoresizingMaskIntoConstraints = false\n            self.buttons.append(button)\n            return button\n        }\n\n        for (index, segment) in self.segments.enumerated() {\n            let button = makeButton(index: index, segment: segment)\n            self.addSubview(button)\n        }\n\n        let uniformWidth: CGFloat\n        if self.rowCount > 1 || !self.stackedIcons {\n            uniformWidth = self.applyUniformSegmentWidth(maxAllowedWidth: maxAllowedSegmentWidth)\n            if uniformWidth > 0 {\n                self.segmentWidths = Array(repeating: uniformWidth, count: self.buttons.count)\n            }\n        } else {\n            self.segmentWidths = self.applyNonUniformSegmentWidths(\n                totalWidth: width,\n                outerPadding: outerPadding,\n                minimumGap: minimumGap)\n            uniformWidth = 0\n        }\n\n        self.applyLayout(\n            outerPadding: outerPadding,\n            minimumGap: minimumGap,\n            uniformWidth: uniformWidth)\n        if width > 0 {\n            self.preferredWidth = width\n            self.frame.size.width = width\n        }\n\n        self.updateButtonStyles()\n    }\n\n    override func layout() {\n        super.layout()\n        self.lightModeOverlayLayer.frame = self.bounds\n    }\n\n    override func viewDidChangeEffectiveAppearance() {\n        super.viewDidChangeEffectiveAppearance()\n        self.updateLightModeStyling()\n        self.updateButtonStyles()\n    }\n\n    override func viewDidMoveToWindow() {\n        super.viewDidMoveToWindow()\n        self.window?.acceptsMouseMovedEvents = true\n    }\n\n    override func updateTrackingAreas() {\n        super.updateTrackingAreas()\n\n        if let hoverTrackingArea {\n            self.removeTrackingArea(hoverTrackingArea)\n        }\n\n        let trackingArea = NSTrackingArea(\n            rect: .zero,\n            options: [\n                .activeAlways,\n                .inVisibleRect,\n                .mouseEnteredAndExited,\n                .mouseMoved,\n            ],\n            owner: self,\n            userInfo: nil)\n        self.addTrackingArea(trackingArea)\n        self.hoverTrackingArea = trackingArea\n    }\n\n    override func mouseMoved(with event: NSEvent) {\n        let location = self.convert(event.locationInWindow, from: nil)\n        let hoveredTag = self.buttons.first(where: { $0.frame.contains(location) })?.tag\n        guard hoveredTag != self.hoveredButtonTag else { return }\n        self.hoveredButtonTag = hoveredTag\n        self.updateButtonStyles()\n    }\n\n    override func mouseExited(with event: NSEvent) {\n        guard self.hoveredButtonTag != nil else { return }\n        self.hoveredButtonTag = nil\n        self.updateButtonStyles()\n    }\n\n    private func applyLayout(\n        outerPadding: CGFloat,\n        minimumGap: CGFloat,\n        uniformWidth: CGFloat)\n    {\n        if self.rowCount > 1 {\n            self.applyMultiRowLayout(\n                rowCount: self.rowCount,\n                outerPadding: outerPadding,\n                minimumGap: minimumGap,\n                uniformWidth: uniformWidth)\n            return\n        }\n\n        if self.buttons.count == 2 {\n            let left = self.buttons[0]\n            let right = self.buttons[1]\n            let gap = right.leadingAnchor.constraint(greaterThanOrEqualTo: left.trailingAnchor, constant: minimumGap)\n            gap.priority = .defaultHigh\n            NSLayoutConstraint.activate([\n                left.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: outerPadding),\n                left.centerYAnchor.constraint(equalTo: self.centerYAnchor),\n                right.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -outerPadding),\n                right.centerYAnchor.constraint(equalTo: self.centerYAnchor),\n                gap,\n            ])\n            return\n        }\n\n        if self.buttons.count == 3 {\n            let left = self.buttons[0]\n            let mid = self.buttons[1]\n            let right = self.buttons[2]\n\n            let leftGap = mid.leadingAnchor.constraint(greaterThanOrEqualTo: left.trailingAnchor, constant: minimumGap)\n            leftGap.priority = .defaultHigh\n            let rightGap = right.leadingAnchor.constraint(\n                greaterThanOrEqualTo: mid.trailingAnchor,\n                constant: minimumGap)\n            rightGap.priority = .defaultHigh\n\n            NSLayoutConstraint.activate([\n                left.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: outerPadding),\n                left.centerYAnchor.constraint(equalTo: self.centerYAnchor),\n                mid.centerXAnchor.constraint(equalTo: self.centerXAnchor),\n                mid.centerYAnchor.constraint(equalTo: self.centerYAnchor),\n                right.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -outerPadding),\n                right.centerYAnchor.constraint(equalTo: self.centerYAnchor),\n                leftGap,\n                rightGap,\n            ])\n            return\n        }\n\n        if self.buttons.count >= 4 {\n            let widths = self.segmentWidths.isEmpty\n                ? self.buttons.map { ceil($0.fittingSize.width) }\n                : self.segmentWidths\n            let layoutWidth = self.preferredWidth > 0 ? self.preferredWidth : self.bounds.width\n            let availableWidth = max(0, layoutWidth - outerPadding * 2)\n            let gaps = max(1, widths.count - 1)\n            let computedGap = gaps > 0\n                ? max(minimumGap, (availableWidth - widths.reduce(0, +)) / CGFloat(gaps))\n                : 0\n            let rowContainer = NSView()\n            rowContainer.translatesAutoresizingMaskIntoConstraints = false\n            self.addSubview(rowContainer)\n\n            NSLayoutConstraint.activate([\n                rowContainer.topAnchor.constraint(equalTo: self.topAnchor),\n                rowContainer.bottomAnchor.constraint(equalTo: self.bottomAnchor),\n                rowContainer.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: outerPadding),\n                rowContainer.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -outerPadding),\n            ])\n\n            var xOffset: CGFloat = 0\n            for (index, button) in self.buttons.enumerated() {\n                let width = index < widths.count ? widths[index] : 0\n                if self.stackedIcons {\n                    NSLayoutConstraint.activate([\n                        button.leadingAnchor.constraint(equalTo: rowContainer.leadingAnchor, constant: xOffset),\n                        button.topAnchor.constraint(equalTo: rowContainer.topAnchor),\n                    ])\n                } else {\n                    NSLayoutConstraint.activate([\n                        button.leadingAnchor.constraint(equalTo: rowContainer.leadingAnchor, constant: xOffset),\n                        button.centerYAnchor.constraint(equalTo: rowContainer.centerYAnchor),\n                    ])\n                }\n                xOffset += width + computedGap\n            }\n            return\n        }\n\n        if let first = self.buttons.first {\n            NSLayoutConstraint.activate([\n                first.centerXAnchor.constraint(equalTo: self.centerXAnchor),\n                first.centerYAnchor.constraint(equalTo: self.centerYAnchor),\n            ])\n        }\n    }\n\n    private func applyMultiRowLayout(\n        rowCount: Int,\n        outerPadding: CGFloat,\n        minimumGap: CGFloat,\n        uniformWidth: CGFloat)\n    {\n        let rows = Self.splitRows(for: self.buttons, rowCount: rowCount)\n        let columns = rows.map(\\.count).max() ?? 0\n        let layoutWidth = self.preferredWidth > 0 ? self.preferredWidth : self.bounds.width\n        let availableWidth = max(0, layoutWidth - outerPadding * 2)\n        let gaps = max(1, columns - 1)\n        let totalWidth = uniformWidth * CGFloat(columns)\n        let computedGap = gaps > 0\n            ? max(minimumGap, (availableWidth - totalWidth) / CGFloat(gaps))\n            : 0\n        let gridContainer = NSView()\n        gridContainer.translatesAutoresizingMaskIntoConstraints = false\n        self.addSubview(gridContainer)\n\n        NSLayoutConstraint.activate([\n            gridContainer.topAnchor.constraint(equalTo: self.topAnchor),\n            gridContainer.bottomAnchor.constraint(equalTo: self.bottomAnchor),\n            gridContainer.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: outerPadding),\n            gridContainer.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -outerPadding),\n        ])\n\n        var rowViews: [NSView] = []\n        for _ in 0..<rowCount {\n            let row = NSView()\n            row.translatesAutoresizingMaskIntoConstraints = false\n            gridContainer.addSubview(row)\n            rowViews.append(row)\n        }\n\n        var rowConstraints: [NSLayoutConstraint] = []\n        for (index, row) in rowViews.enumerated() {\n            rowConstraints.append(row.leadingAnchor.constraint(equalTo: gridContainer.leadingAnchor))\n            rowConstraints.append(row.trailingAnchor.constraint(equalTo: gridContainer.trailingAnchor))\n            rowConstraints.append(row.heightAnchor.constraint(equalToConstant: self.rowHeight))\n            if index == 0 {\n                rowConstraints.append(row.topAnchor.constraint(equalTo: gridContainer.topAnchor))\n            } else {\n                rowConstraints.append(row.topAnchor.constraint(\n                    equalTo: rowViews[index - 1].bottomAnchor,\n                    constant: self.rowSpacing))\n            }\n            if index == rowViews.count - 1 {\n                rowConstraints.append(row.bottomAnchor.constraint(equalTo: gridContainer.bottomAnchor))\n            }\n        }\n        NSLayoutConstraint.activate(rowConstraints)\n\n        for (rowIndex, rowButtons) in rows.enumerated() {\n            guard rowIndex < rowViews.count else { continue }\n            let rowView = rowViews[rowIndex]\n            for (columnIndex, button) in rowButtons.enumerated() {\n                let xOffset = CGFloat(columnIndex) * (uniformWidth + computedGap)\n                NSLayoutConstraint.activate([\n                    button.leadingAnchor.constraint(equalTo: gridContainer.leadingAnchor, constant: xOffset),\n                    button.centerYAnchor.constraint(equalTo: rowView.centerYAnchor),\n                ])\n            }\n        }\n    }\n\n    private static func switcherRowCount(\n        width: CGFloat,\n        count: Int,\n        maxAllowedSegmentWidth: CGFloat,\n        stackedIcons: Bool) -> Int\n    {\n        guard count > 1 else { return 1 }\n        let maxRows = min(4, count)\n        let fourRowThreshold = 15\n        let minimumComfortableAverage: CGFloat = stackedIcons ? 50 : 54\n        if count >= fourRowThreshold { return maxRows }\n        if maxAllowedSegmentWidth >= minimumComfortableAverage { return 1 }\n\n        for rows in 2...maxRows {\n            let perRow = self.layoutCount(for: count, rows: rows)\n            let outerPadding = self.switcherOuterPadding(for: width, count: perRow, minimumGap: 1)\n            let allowedWidth = self.maxAllowedUniformSegmentWidth(\n                for: width,\n                count: perRow,\n                outerPadding: outerPadding,\n                minimumGap: 1)\n            if allowedWidth >= minimumComfortableAverage { return rows }\n        }\n\n        return maxRows\n    }\n\n    private static func layoutCount(for count: Int, rows: Int) -> Int {\n        guard rows > 0 else { return count }\n        return Int(ceil(Double(count) / Double(rows)))\n    }\n\n    private static func splitRows(for buttons: [NSButton], rowCount: Int) -> [[NSButton]] {\n        guard rowCount > 1 else { return [buttons] }\n        let base = buttons.count / rowCount\n        let extra = buttons.count % rowCount\n        var rows: [[NSButton]] = []\n        var start = 0\n        for index in 0..<rowCount {\n            let size = base + (index < extra ? 1 : 0)\n            if size == 0 {\n                rows.append([])\n                continue\n            }\n            let end = min(buttons.count, start + size)\n            rows.append(Array(buttons[start..<end]))\n            start = end\n        }\n        return rows\n    }\n\n    private static func switcherOuterPadding(for width: CGFloat, count: Int, minimumGap: CGFloat) -> CGFloat {\n        // Align with the card's left/right content grid when possible.\n        let preferred: CGFloat = 16\n        let reduced: CGFloat = 10\n        let minimal: CGFloat = 6\n\n        func averageButtonWidth(outerPadding: CGFloat) -> CGFloat {\n            let available = width - outerPadding * 2 - minimumGap * CGFloat(max(0, count - 1))\n            guard count > 0 else { return 0 }\n            return available / CGFloat(count)\n        }\n\n        // Only sacrifice padding when we'd otherwise squeeze buttons into unreadable widths.\n        let minimumComfortableAverage: CGFloat = count >= 5 ? 50 : 54\n\n        if averageButtonWidth(outerPadding: preferred) >= minimumComfortableAverage { return preferred }\n        if averageButtonWidth(outerPadding: reduced) >= minimumComfortableAverage { return reduced }\n        return minimal\n    }\n\n    @available(*, unavailable)\n    required init?(coder: NSCoder) {\n        nil\n    }\n\n    override var intrinsicContentSize: NSSize {\n        NSSize(width: self.preferredWidth, height: self.frame.size.height)\n    }\n\n    @objc private func handleSelection(_ sender: NSButton) {\n        let index = sender.tag\n        guard self.segments.indices.contains(index) else { return }\n        for (idx, button) in self.buttons.enumerated() {\n            button.state = (idx == index) ? .on : .off\n        }\n        self.updateButtonStyles()\n        self.onSelect(self.segments[index].selection)\n    }\n\n    private func updateButtonStyles() {\n        for button in self.buttons {\n            let isSelected = button.state == .on\n            let isHovered = self.hoveredButtonTag == button.tag\n            button.contentTintColor = isSelected ? self.selectedTextColor : self.unselectedTextColor\n            button.layer?.backgroundColor = if isSelected {\n                self.selectedBackground\n            } else if isHovered {\n                self.hoverPlateColor()\n            } else {\n                self.unselectedBackground\n            }\n            self.updateWeeklyIndicatorVisibility(for: button)\n            (button as? StackedToggleButton)?.setContentTintColor(button.contentTintColor)\n            (button as? InlineIconToggleButton)?.setContentTintColor(button.contentTintColor)\n        }\n    }\n\n    private func isLightMode() -> Bool {\n        self.effectiveAppearance.bestMatch(from: [.aqua, .darkAqua]) == .aqua\n    }\n\n    private func updateLightModeStyling() {\n        guard self.isLightMode() else {\n            self.lightModeOverlayLayer.backgroundColor = nil\n            return\n        }\n        // The menu card background is very bright in light mode; add a subtle neutral wash to ground the switcher.\n        self.lightModeOverlayLayer.backgroundColor = NSColor.black.withAlphaComponent(0.035).cgColor\n    }\n\n    private func hoverPlateColor() -> CGColor {\n        if self.isLightMode() {\n            return NSColor.black.withAlphaComponent(0.095).cgColor\n        }\n        return NSColor.labelColor.withAlphaComponent(0.06).cgColor\n    }\n\n    /// Cache for button width measurements to avoid repeated layout passes.\n    private static var buttonWidthCache: [ObjectIdentifier: CGFloat] = [:]\n\n    private static func maxToggleWidth(for button: NSButton) -> CGFloat {\n        let buttonId = ObjectIdentifier(button)\n\n        // Return cached value if available.\n        if let cached = buttonWidthCache[buttonId] {\n            return cached\n        }\n\n        let originalState = button.state\n        defer { button.state = originalState }\n\n        button.state = .off\n        button.layoutSubtreeIfNeeded()\n        let offWidth = button.fittingSize.width\n\n        button.state = .on\n        button.layoutSubtreeIfNeeded()\n        let onWidth = button.fittingSize.width\n\n        let maxWidth = max(offWidth, onWidth)\n        self.buttonWidthCache[buttonId] = maxWidth\n        return maxWidth\n    }\n\n    private static func clearButtonWidthCache() {\n        self.buttonWidthCache.removeAll()\n    }\n\n    private func applyUniformSegmentWidth(maxAllowedWidth: CGFloat) -> CGFloat {\n        guard !self.buttons.isEmpty else { return 0 }\n\n        var desiredWidths: [CGFloat] = []\n        desiredWidths.reserveCapacity(self.buttons.count)\n\n        for (index, button) in self.buttons.enumerated() {\n            if self.stackedIcons,\n               self.segments.indices.contains(index)\n            {\n                let font = NSFont.systemFont(ofSize: NSFont.smallSystemFontSize)\n                let titleWidth = ceil(\n                    (self.segments[index].title as NSString).size(withAttributes: [.font: font])\n                        .width)\n                let contentPadding: CGFloat = 4 + 4\n                let extraSlack: CGFloat = 1\n                desiredWidths.append(ceil(titleWidth + contentPadding + extraSlack))\n            } else {\n                desiredWidths.append(ceil(Self.maxToggleWidth(for: button)))\n            }\n        }\n\n        let maxDesired = desiredWidths.max() ?? 0\n        let evenMaxDesired = maxDesired.truncatingRemainder(dividingBy: 2) == 0 ? maxDesired : maxDesired + 1\n        let evenMaxAllowed = maxAllowedWidth > 0\n            ? (maxAllowedWidth.truncatingRemainder(dividingBy: 2) == 0 ? maxAllowedWidth : maxAllowedWidth - 1)\n            : 0\n        let finalWidth: CGFloat = if evenMaxAllowed > 0 {\n            min(evenMaxDesired, evenMaxAllowed)\n        } else {\n            evenMaxDesired\n        }\n\n        if finalWidth > 0 {\n            for button in self.buttons {\n                button.widthAnchor.constraint(equalToConstant: finalWidth).isActive = true\n            }\n        }\n\n        return finalWidth\n    }\n\n    @discardableResult\n    private func applyNonUniformSegmentWidths(\n        totalWidth: CGFloat,\n        outerPadding: CGFloat,\n        minimumGap: CGFloat) -> [CGFloat]\n    {\n        guard !self.buttons.isEmpty else { return [] }\n\n        let count = self.buttons.count\n        let available = totalWidth -\n            outerPadding * 2 -\n            minimumGap * CGFloat(max(0, count - 1))\n        guard available > 0 else { return [] }\n\n        func evenFloor(_ value: CGFloat) -> CGFloat {\n            var v = floor(value)\n            if Int(v) % 2 != 0 { v -= 1 }\n            return v\n        }\n\n        let desired = self.buttons.map { ceil(Self.maxToggleWidth(for: $0)) }\n        let desiredSum = desired.reduce(0, +)\n        let avg = floor(available / CGFloat(count))\n        let minWidth = max(24, min(40, avg))\n\n        var widths: [CGFloat]\n        if desiredSum <= available {\n            widths = desired\n        } else {\n            let totalCapacity = max(0, desiredSum - minWidth * CGFloat(count))\n            if totalCapacity <= 0 {\n                widths = Array(repeating: available / CGFloat(count), count: count)\n            } else {\n                let overflow = desiredSum - available\n                widths = desired.map { desiredWidth in\n                    let capacity = max(0, desiredWidth - minWidth)\n                    let shrink = overflow * (capacity / totalCapacity)\n                    return desiredWidth - shrink\n                }\n            }\n        }\n\n        widths = widths.map { max(minWidth, evenFloor($0)) }\n        var used = widths.reduce(0, +)\n\n        while available - used >= 2 {\n            if let best = widths.indices\n                .filter({ desired[$0] - widths[$0] >= 2 })\n                .max(by: { lhs, rhs in\n                    (desired[lhs] - widths[lhs]) < (desired[rhs] - widths[rhs])\n                })\n            {\n                widths[best] += 2\n                used += 2\n                continue\n            }\n\n            guard let best = widths.indices.min(by: { lhs, rhs in widths[lhs] < widths[rhs] }) else { break }\n            widths[best] += 2\n            used += 2\n        }\n\n        for (index, button) in self.buttons.enumerated() where index < widths.count {\n            button.widthAnchor.constraint(equalToConstant: widths[index]).isActive = true\n        }\n\n        return widths\n    }\n\n    private static func maxAllowedUniformSegmentWidth(\n        for totalWidth: CGFloat,\n        count: Int,\n        outerPadding: CGFloat,\n        minimumGap: CGFloat) -> CGFloat\n    {\n        guard count > 0 else { return 0 }\n        let available = totalWidth -\n            outerPadding * 2 -\n            minimumGap * CGFloat(max(0, count - 1))\n        guard available > 0 else { return 0 }\n        return floor(available / CGFloat(count))\n    }\n\n    private static func paddedImage(_ image: NSImage, leading: CGFloat) -> NSImage {\n        let size = NSSize(width: image.size.width + leading, height: image.size.height)\n        let newImage = NSImage(size: size)\n        newImage.lockFocus()\n        let y = (size.height - image.size.height) / 2\n        image.draw(\n            at: NSPoint(x: leading, y: y),\n            from: NSRect(origin: .zero, size: image.size),\n            operation: .sourceOver,\n            fraction: 1.0)\n        newImage.unlockFocus()\n        newImage.isTemplate = image.isTemplate\n        return newImage\n    }\n\n    private func addWeeklyIndicator(to view: NSView, selection: ProviderSwitcherSelection, remainingPercent: Double?) {\n        guard let remainingPercent else { return }\n\n        let track = NSView()\n        track.wantsLayer = true\n        track.layer?.backgroundColor = NSColor.tertiaryLabelColor.withAlphaComponent(0.22).cgColor\n        track.layer?.cornerRadius = 2\n        track.layer?.masksToBounds = true\n        track.translatesAutoresizingMaskIntoConstraints = false\n        view.addSubview(track)\n\n        let fill = NSView()\n        fill.wantsLayer = true\n        fill.layer?.backgroundColor = Self.weeklyIndicatorColor(for: selection).cgColor\n        fill.layer?.cornerRadius = 2\n        fill.translatesAutoresizingMaskIntoConstraints = false\n        track.addSubview(fill)\n\n        let ratio = CGFloat(max(0, min(1, remainingPercent / 100)))\n\n        NSLayoutConstraint.activate([\n            track.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 6),\n            track.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -6),\n            track.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -1),\n            track.heightAnchor.constraint(equalToConstant: 4),\n            fill.leadingAnchor.constraint(equalTo: track.leadingAnchor),\n            fill.topAnchor.constraint(equalTo: track.topAnchor),\n            fill.bottomAnchor.constraint(equalTo: track.bottomAnchor),\n        ])\n\n        fill.widthAnchor.constraint(equalTo: track.widthAnchor, multiplier: ratio).isActive = true\n\n        self.weeklyIndicators[ObjectIdentifier(view)] = WeeklyIndicator(track: track, fill: fill)\n        self.updateWeeklyIndicatorVisibility(for: view)\n    }\n\n    private func updateWeeklyIndicatorVisibility(for view: NSView) {\n        guard let indicator = self.weeklyIndicators[ObjectIdentifier(view)] else { return }\n        let isSelected = (view as? NSButton)?.state == .on\n        indicator.track.isHidden = isSelected\n        indicator.fill.isHidden = isSelected\n    }\n\n    private static func weeklyIndicatorColor(for selection: ProviderSwitcherSelection) -> NSColor {\n        switch selection {\n        case let .provider(provider):\n            let color = ProviderDescriptorRegistry.descriptor(for: provider).branding.color\n            return NSColor(deviceRed: color.red, green: color.green, blue: color.blue, alpha: 1)\n        case .overview:\n            return NSColor.secondaryLabelColor\n        }\n    }\n\n    private static func overviewIcon() -> NSImage {\n        if let symbol = NSImage(systemSymbolName: \"square.grid.2x2\", accessibilityDescription: nil) {\n            return symbol\n        }\n        return NSImage(size: NSSize(width: 16, height: 16))\n    }\n\n    private static func switcherTitle(for provider: UsageProvider) -> String {\n        ProviderDescriptorRegistry.descriptor(for: provider).metadata.displayName\n    }\n}\n\nfinal class TokenAccountSwitcherView: NSView {\n    private let accounts: [ProviderTokenAccount]\n    private let onSelect: (Int) -> Void\n    private var selectedIndex: Int\n    private var buttons: [NSButton] = []\n    private let rowSpacing: CGFloat = 4\n    private let rowHeight: CGFloat = 26\n    private let selectedBackground = NSColor.controlAccentColor.cgColor\n    private let unselectedBackground = NSColor.clear.cgColor\n    private let selectedTextColor = NSColor.white\n    private let unselectedTextColor = NSColor.secondaryLabelColor\n\n    init(accounts: [ProviderTokenAccount], selectedIndex: Int, width: CGFloat, onSelect: @escaping (Int) -> Void) {\n        self.accounts = accounts\n        self.onSelect = onSelect\n        self.selectedIndex = min(max(selectedIndex, 0), max(0, accounts.count - 1))\n        let useTwoRows = accounts.count > 3\n        let rows = useTwoRows ? 2 : 1\n        let height = self.rowHeight * CGFloat(rows) + (useTwoRows ? self.rowSpacing : 0)\n        super.init(frame: NSRect(x: 0, y: 0, width: width, height: height))\n        self.wantsLayer = true\n        self.buildButtons(useTwoRows: useTwoRows)\n        self.updateButtonStyles()\n    }\n\n    @available(*, unavailable)\n    required init?(coder: NSCoder) {\n        nil\n    }\n\n    private func buildButtons(useTwoRows: Bool) {\n        let perRow = useTwoRows ? Int(ceil(Double(self.accounts.count) / 2.0)) : self.accounts.count\n        let rows: [[ProviderTokenAccount]] = {\n            if !useTwoRows { return [self.accounts] }\n            let first = Array(self.accounts.prefix(perRow))\n            let second = Array(self.accounts.dropFirst(perRow))\n            return [first, second]\n        }()\n\n        let stack = NSStackView()\n        stack.orientation = .vertical\n        stack.alignment = .centerX\n        stack.spacing = self.rowSpacing\n        stack.translatesAutoresizingMaskIntoConstraints = false\n\n        var globalIndex = 0\n        for rowAccounts in rows {\n            let row = NSStackView()\n            row.orientation = .horizontal\n            row.alignment = .centerY\n            row.distribution = .fillEqually\n            row.spacing = self.rowSpacing\n            row.translatesAutoresizingMaskIntoConstraints = false\n\n            for account in rowAccounts {\n                let button = PaddedToggleButton(\n                    title: account.displayName,\n                    target: self,\n                    action: #selector(self.handleSelect))\n                button.tag = globalIndex\n                button.toolTip = account.displayName\n                button.isBordered = false\n                button.setButtonType(.toggle)\n                button.controlSize = .small\n                button.font = NSFont.systemFont(ofSize: NSFont.smallSystemFontSize)\n                button.wantsLayer = true\n                button.layer?.cornerRadius = 6\n                row.addArrangedSubview(button)\n                self.buttons.append(button)\n                globalIndex += 1\n            }\n\n            stack.addArrangedSubview(row)\n        }\n\n        self.addSubview(stack)\n        NSLayoutConstraint.activate([\n            stack.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 6),\n            stack.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -6),\n            stack.topAnchor.constraint(equalTo: self.topAnchor),\n            stack.bottomAnchor.constraint(equalTo: self.bottomAnchor),\n            stack.heightAnchor.constraint(equalToConstant: self.rowHeight * CGFloat(rows.count) +\n                (useTwoRows ? self.rowSpacing : 0)),\n        ])\n    }\n\n    private func updateButtonStyles() {\n        for (index, button) in self.buttons.enumerated() {\n            let selected = index == self.selectedIndex\n            button.state = selected ? .on : .off\n            button.layer?.backgroundColor = selected ? self.selectedBackground : self.unselectedBackground\n            button.contentTintColor = selected ? self.selectedTextColor : self.unselectedTextColor\n        }\n    }\n\n    @objc private func handleSelect(_ sender: NSButton) {\n        let index = sender.tag\n        guard index >= 0, index < self.accounts.count else { return }\n        self.selectedIndex = index\n        self.updateButtonStyles()\n        self.onSelect(index)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/StatusItemController.swift",
    "content": "import AppKit\nimport CodexBarCore\nimport Observation\nimport QuartzCore\nimport SwiftUI\n\n// MARK: - Status item controller (AppKit-hosted icons, SwiftUI popovers)\n\n@MainActor\nprotocol StatusItemControlling: AnyObject {\n    func openMenuFromShortcut()\n}\n\n@MainActor\nfinal class StatusItemController: NSObject, NSMenuDelegate, StatusItemControlling {\n    // Disable SwiftUI menu cards + menu refresh work in tests to avoid swiftpm-testing-helper crashes.\n    static var menuCardRenderingEnabled = !SettingsStore.isRunningTests\n    static var menuRefreshEnabled = !SettingsStore.isRunningTests\n    typealias Factory = (UsageStore, SettingsStore, AccountInfo, UpdaterProviding, PreferencesSelection)\n        -> StatusItemControlling\n    static let defaultFactory: Factory = { store, settings, account, updater, selection in\n        StatusItemController(\n            store: store,\n            settings: settings,\n            account: account,\n            updater: updater,\n            preferencesSelection: selection)\n    }\n\n    static var factory: Factory = StatusItemController.defaultFactory\n\n    let store: UsageStore\n    let settings: SettingsStore\n    let account: AccountInfo\n    let updater: UpdaterProviding\n    private let statusBar: NSStatusBar\n    var statusItem: NSStatusItem\n    var statusItems: [UsageProvider: NSStatusItem] = [:]\n    var lastMenuProvider: UsageProvider?\n    var menuProviders: [ObjectIdentifier: UsageProvider] = [:]\n    var menuContentVersion: Int = 0\n    var menuVersions: [ObjectIdentifier: Int] = [:]\n    var mergedMenu: NSMenu?\n    var providerMenus: [UsageProvider: NSMenu] = [:]\n    var fallbackMenu: NSMenu?\n    var openMenus: [ObjectIdentifier: NSMenu] = [:]\n    var menuRefreshTasks: [ObjectIdentifier: Task<Void, Never>] = [:]\n    var blinkTask: Task<Void, Never>?\n    var loginTask: Task<Void, Never>? {\n        didSet { self.refreshMenusForLoginStateChange() }\n    }\n\n    var creditsPurchaseWindow: OpenAICreditsPurchaseWindowController?\n\n    var activeLoginProvider: UsageProvider? {\n        didSet {\n            if oldValue != self.activeLoginProvider {\n                self.refreshMenusForLoginStateChange()\n            }\n        }\n    }\n\n    var blinkStates: [UsageProvider: BlinkState] = [:]\n    var blinkAmounts: [UsageProvider: CGFloat] = [:]\n    var wiggleAmounts: [UsageProvider: CGFloat] = [:]\n    var tiltAmounts: [UsageProvider: CGFloat] = [:]\n    var blinkForceUntil: Date?\n    var loginPhase: LoginPhase = .idle {\n        didSet {\n            if oldValue != self.loginPhase {\n                self.refreshMenusForLoginStateChange()\n            }\n        }\n    }\n\n    let preferencesSelection: PreferencesSelection\n    var animationDriver: DisplayLinkDriver?\n    var animationPhase: Double = 0\n    var animationPattern: LoadingPattern = .knightRider\n    private var lastConfigRevision: Int\n    private var lastProviderOrder: [UsageProvider]\n    private var lastMergeIcons: Bool\n    private var lastSwitcherShowsIcons: Bool\n    private var lastObservedUsageBarsShowUsed: Bool\n    /// Tracks which `usageBarsShowUsed` mode the provider switcher was built with.\n    /// Used to decide whether we can \"smart update\" menu content without rebuilding the switcher.\n    var lastSwitcherUsageBarsShowUsed: Bool\n    /// Tracks whether the merged-menu switcher was built with the Overview tab visible.\n    /// Used to force switcher rebuilds when Overview availability toggles.\n    var lastSwitcherIncludesOverview: Bool = false\n    /// Tracks which providers the merged menu's switcher was built with, to detect when it needs full rebuild.\n    var lastSwitcherProviders: [UsageProvider] = []\n    /// Tracks which switcher tab state was used for the current merged-menu switcher instance.\n    var lastMergedSwitcherSelection: ProviderSwitcherSelection?\n    let loginLogger = CodexBarLog.logger(LogCategories.login)\n    var selectedMenuProvider: UsageProvider? {\n        get { self.settings.selectedMenuProvider }\n        set { self.settings.selectedMenuProvider = newValue }\n    }\n\n    struct BlinkState {\n        var nextBlink: Date\n        var blinkStart: Date?\n        var pendingSecondStart: Date?\n        var effect: MotionEffect = .blink\n\n        static func randomDelay() -> TimeInterval {\n            Double.random(in: 3...12)\n        }\n    }\n\n    enum MotionEffect {\n        case blink\n        case wiggle\n        case tilt\n    }\n\n    enum LoginPhase {\n        case idle\n        case requesting\n        case waitingBrowser\n    }\n\n    func menuBarMetricWindow(for provider: UsageProvider, snapshot: UsageSnapshot?) -> RateWindow? {\n        switch self.settings.menuBarMetricPreference(for: provider) {\n        case .primary:\n            return snapshot?.primary ?? snapshot?.secondary\n        case .secondary:\n            return snapshot?.secondary ?? snapshot?.primary\n        case .average:\n            guard let primary = snapshot?.primary, let secondary = snapshot?.secondary else {\n                return snapshot?.primary ?? snapshot?.secondary\n            }\n            let usedPercent = (primary.usedPercent + secondary.usedPercent) / 2\n            return RateWindow(usedPercent: usedPercent, windowMinutes: nil, resetsAt: nil, resetDescription: nil)\n        case .automatic:\n            if provider == .factory || provider == .kimi {\n                return snapshot?.secondary ?? snapshot?.primary\n            }\n            if provider == .copilot,\n               let primary = snapshot?.primary,\n               let secondary = snapshot?.secondary\n            {\n                // Copilot can expose chat + completions quotas; show the more constrained one by default.\n                return primary.usedPercent >= secondary.usedPercent ? primary : secondary\n            }\n            return snapshot?.primary ?? snapshot?.secondary\n        }\n    }\n\n    init(\n        store: UsageStore,\n        settings: SettingsStore,\n        account: AccountInfo,\n        updater: UpdaterProviding,\n        preferencesSelection: PreferencesSelection,\n        statusBar: NSStatusBar = .system)\n    {\n        if SettingsStore.isRunningTests {\n            _ = NSApplication.shared\n        }\n        self.store = store\n        self.settings = settings\n        self.account = account\n        self.updater = updater\n        self.preferencesSelection = preferencesSelection\n        self.lastConfigRevision = settings.configRevision\n        self.lastProviderOrder = settings.providerOrder\n        self.lastMergeIcons = settings.mergeIcons\n        self.lastSwitcherShowsIcons = settings.switcherShowsIcons\n        self.lastObservedUsageBarsShowUsed = settings.usageBarsShowUsed\n        self.lastSwitcherUsageBarsShowUsed = settings.usageBarsShowUsed\n        self.statusBar = statusBar\n        let item = statusBar.statusItem(withLength: NSStatusItem.variableLength)\n        // Ensure the icon is rendered at 1:1 without resampling (crisper edges for template images).\n        item.button?.imageScaling = .scaleNone\n        self.statusItem = item\n        // Status items for individual providers are now created lazily in updateVisibility()\n        super.init()\n        self.wireBindings()\n        self.updateIcons()\n        self.updateVisibility()\n        NotificationCenter.default.addObserver(\n            self,\n            selector: #selector(self.handleDebugReplayNotification(_:)),\n            name: .codexbarDebugReplayAllAnimations,\n            object: nil)\n        NotificationCenter.default.addObserver(\n            self,\n            selector: #selector(self.handleDebugBlinkNotification),\n            name: .codexbarDebugBlinkNow,\n            object: nil)\n        NotificationCenter.default.addObserver(\n            self,\n            selector: #selector(self.handleProviderConfigDidChange),\n            name: .codexbarProviderConfigDidChange,\n            object: nil)\n    }\n\n    private func wireBindings() {\n        self.observeStoreChanges()\n        self.observeDebugForceAnimation()\n        self.observeSettingsChanges()\n        self.observeUpdaterChanges()\n    }\n\n    private func observeStoreChanges() {\n        withObservationTracking {\n            _ = self.store.menuObservationToken\n        } onChange: { [weak self] in\n            Task { @MainActor [weak self] in\n                guard let self else { return }\n                self.observeStoreChanges()\n                self.invalidateMenus()\n                self.updateIcons()\n                self.updateBlinkingState()\n            }\n        }\n    }\n\n    private func observeDebugForceAnimation() {\n        withObservationTracking {\n            _ = self.store.debugForceAnimation\n        } onChange: { [weak self] in\n            Task { @MainActor [weak self] in\n                guard let self else { return }\n                self.observeDebugForceAnimation()\n                self.updateVisibility()\n                self.updateBlinkingState()\n            }\n        }\n    }\n\n    private func observeSettingsChanges() {\n        withObservationTracking {\n            _ = self.settings.menuObservationToken\n        } onChange: { [weak self] in\n            Task { @MainActor [weak self] in\n                guard let self else { return }\n                self.observeSettingsChanges()\n                self.handleSettingsChange(reason: \"observation\")\n            }\n        }\n    }\n\n    func handleProviderConfigChange(reason: String) {\n        self.handleSettingsChange(reason: \"config:\\(reason)\")\n    }\n\n    @objc private func handleProviderConfigDidChange(_ notification: Notification) {\n        let reason = notification.userInfo?[\"reason\"] as? String ?? \"unknown\"\n        if let source = notification.object as? SettingsStore,\n           source !== self.settings\n        {\n            if let config = notification.userInfo?[\"config\"] as? CodexBarConfig {\n                self.settings.applyExternalConfig(config, reason: \"external-\\(reason)\")\n            } else {\n                self.settings.reloadConfig(reason: \"external-\\(reason)\")\n            }\n        }\n        self.handleProviderConfigChange(reason: \"notification:\\(reason)\")\n    }\n\n    private func observeUpdaterChanges() {\n        withObservationTracking {\n            _ = self.updater.updateStatus.isUpdateReady\n        } onChange: { [weak self] in\n            Task { @MainActor [weak self] in\n                guard let self else { return }\n                self.observeUpdaterChanges()\n                self.invalidateMenus()\n            }\n        }\n    }\n\n    private func invalidateMenus() {\n        self.menuContentVersion &+= 1\n        // Don't refresh menus while they're open - wait until they close and reopen\n        // This prevents expensive rebuilds while user is navigating the menu\n        guard self.openMenus.isEmpty else { return }\n        self.refreshOpenMenusIfNeeded()\n        Task { @MainActor in\n            // AppKit can ignore menu mutations while tracking; retry on the next run loop.\n            await Task.yield()\n            guard self.openMenus.isEmpty else { return }\n            self.refreshOpenMenusIfNeeded()\n        }\n    }\n\n    private func shouldRefreshOpenMenusForProviderSwitcher() -> Bool {\n        var shouldRefresh = false\n        let revision = self.settings.configRevision\n        if revision != self.lastConfigRevision {\n            self.lastConfigRevision = revision\n            shouldRefresh = true\n        }\n        let order = self.settings.providerOrder\n        if order != self.lastProviderOrder {\n            self.lastProviderOrder = order\n            shouldRefresh = true\n        }\n        let mergeIcons = self.settings.mergeIcons\n        if mergeIcons != self.lastMergeIcons {\n            self.lastMergeIcons = mergeIcons\n            shouldRefresh = true\n        }\n        let showsIcons = self.settings.switcherShowsIcons\n        if showsIcons != self.lastSwitcherShowsIcons {\n            self.lastSwitcherShowsIcons = showsIcons\n            shouldRefresh = true\n        }\n        let usageBarsShowUsed = self.settings.usageBarsShowUsed\n        if usageBarsShowUsed != self.lastObservedUsageBarsShowUsed {\n            self.lastObservedUsageBarsShowUsed = usageBarsShowUsed\n            shouldRefresh = true\n        }\n        return shouldRefresh\n    }\n\n    private func handleSettingsChange(reason: String) {\n        let configChanged = self.settings.configRevision != self.lastConfigRevision\n        let orderChanged = self.settings.providerOrder != self.lastProviderOrder\n        let shouldRefreshOpenMenus = self.shouldRefreshOpenMenusForProviderSwitcher()\n        self.invalidateMenus()\n        if orderChanged || configChanged {\n            self.rebuildProviderStatusItems()\n        }\n        self.updateVisibility()\n        self.updateIcons()\n        if shouldRefreshOpenMenus {\n            self.refreshOpenMenusIfNeeded()\n        }\n    }\n\n    private func updateIcons() {\n        // Avoid flicker: when an animation driver is active, store updates can call `updateIcons()` and\n        // briefly overwrite the animated frame with the static (phase=nil) icon.\n        let phase: Double? = self.needsMenuBarIconAnimation() ? self.animationPhase : nil\n        if self.shouldMergeIcons {\n            self.applyIcon(phase: phase)\n            self.attachMenus()\n        } else {\n            UsageProvider.allCases.forEach { self.applyIcon(for: $0, phase: phase) }\n            self.attachMenus(fallback: self.fallbackProvider)\n        }\n        self.updateAnimationState()\n        self.updateBlinkingState()\n    }\n\n    /// Lazily retrieves or creates a status item for the given provider\n    func lazyStatusItem(for provider: UsageProvider) -> NSStatusItem {\n        if let existing = self.statusItems[provider] {\n            return existing\n        }\n        let item = self.statusBar.statusItem(withLength: NSStatusItem.variableLength)\n        item.button?.imageScaling = .scaleNone\n        self.statusItems[provider] = item\n        return item\n    }\n\n    private func updateVisibility() {\n        let anyEnabled = !self.store.enabledProvidersForDisplay().isEmpty\n        let force = self.store.debugForceAnimation\n        let mergeIcons = self.shouldMergeIcons\n        if mergeIcons {\n            self.statusItem.isVisible = anyEnabled || force\n            for item in self.statusItems.values {\n                item.isVisible = false\n            }\n            self.attachMenus()\n        } else {\n            self.statusItem.isVisible = false\n            let fallback = self.fallbackProvider\n            for provider in UsageProvider.allCases {\n                let isEnabled = self.isEnabled(provider)\n                let shouldBeVisible = isEnabled || fallback == provider || force\n                if shouldBeVisible {\n                    let item = self.lazyStatusItem(for: provider)\n                    item.isVisible = true\n                } else if let item = self.statusItems[provider] {\n                    item.isVisible = false\n                }\n            }\n            self.attachMenus(fallback: fallback)\n        }\n        self.updateAnimationState()\n        self.updateBlinkingState()\n    }\n\n    var fallbackProvider: UsageProvider? {\n        // Intentionally uses availability-filtered list: fallback activates when no provider\n        // can actually work, ensuring at least a codex icon is always visible.\n        self.store.enabledProviders().isEmpty ? .codex : nil\n    }\n\n    func isEnabled(_ provider: UsageProvider) -> Bool {\n        self.store.isEnabled(provider)\n    }\n\n    private func refreshMenusForLoginStateChange() {\n        self.invalidateMenus()\n        if self.shouldMergeIcons {\n            self.attachMenus()\n        } else {\n            self.attachMenus(fallback: self.fallbackProvider)\n        }\n    }\n\n    private func attachMenus() {\n        if self.mergedMenu == nil {\n            self.mergedMenu = self.makeMenu()\n        }\n        if self.statusItem.menu !== self.mergedMenu {\n            self.statusItem.menu = self.mergedMenu\n        }\n    }\n\n    private func attachMenus(fallback: UsageProvider? = nil) {\n        for provider in UsageProvider.allCases {\n            // Only access/create the status item if it's actually needed\n            let shouldHaveItem = self.isEnabled(provider) || fallback == provider\n\n            if shouldHaveItem {\n                let item = self.lazyStatusItem(for: provider)\n\n                if self.isEnabled(provider) {\n                    if self.providerMenus[provider] == nil {\n                        self.providerMenus[provider] = self.makeMenu(for: provider)\n                    }\n                    let menu = self.providerMenus[provider]\n                    if item.menu !== menu {\n                        item.menu = menu\n                    }\n                } else if fallback == provider {\n                    if self.fallbackMenu == nil {\n                        self.fallbackMenu = self.makeMenu(for: nil)\n                    }\n                    if item.menu !== self.fallbackMenu {\n                        item.menu = self.fallbackMenu\n                    }\n                }\n            } else if let item = self.statusItems[provider] {\n                // Item exists but is no longer needed - clear its menu\n                if item.menu != nil {\n                    item.menu = nil\n                }\n            }\n        }\n    }\n\n    private func rebuildProviderStatusItems() {\n        for item in self.statusItems.values {\n            self.statusBar.removeStatusItem(item)\n        }\n        self.statusItems.removeAll(keepingCapacity: true)\n\n        for provider in self.settings.orderedProviders() {\n            let item = self.statusBar.statusItem(withLength: NSStatusItem.variableLength)\n            item.button?.imageScaling = .scaleNone\n            self.statusItems[provider] = item\n        }\n    }\n\n    func isVisible(_ provider: UsageProvider) -> Bool {\n        self.store.debugForceAnimation || self.isEnabled(provider) || self.fallbackProvider == provider\n    }\n\n    var shouldMergeIcons: Bool {\n        self.settings.mergeIcons && self.store.enabledProvidersForDisplay().count > 1\n    }\n\n    func switchAccountSubtitle(for target: UsageProvider) -> String? {\n        guard self.loginTask != nil, let provider = self.activeLoginProvider, provider == target else { return nil }\n        let base: String\n        switch self.loginPhase {\n        case .idle: return nil\n        case .requesting: base = \"Requesting login…\"\n        case .waitingBrowser: base = \"Waiting in browser…\"\n        }\n        let prefix = ProviderDescriptorRegistry.descriptor(for: provider).metadata.displayName\n        return \"\\(prefix): \\(base)\"\n    }\n\n    deinit {\n        self.blinkTask?.cancel()\n        self.loginTask?.cancel()\n        NotificationCenter.default.removeObserver(self)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/SyntheticTokenStore.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Security\n\nprotocol SyntheticTokenStoring: Sendable {\n    func loadToken() throws -> String?\n    func storeToken(_ token: String?) throws\n}\n\nenum SyntheticTokenStoreError: LocalizedError {\n    case keychainStatus(OSStatus)\n    case invalidData\n\n    var errorDescription: String? {\n        switch self {\n        case let .keychainStatus(status):\n            \"Keychain error: \\(status)\"\n        case .invalidData:\n            \"Keychain returned invalid data.\"\n        }\n    }\n}\n\nstruct KeychainSyntheticTokenStore: SyntheticTokenStoring {\n    private static let log = CodexBarLog.logger(LogCategories.syntheticTokenStore)\n\n    private let service = \"com.steipete.CodexBar\"\n    private let account = \"synthetic-api-key\"\n\n    func loadToken() throws -> String? {\n        guard !KeychainAccessGate.isDisabled else {\n            Self.log.debug(\"Keychain access disabled; skipping token load\")\n            return nil\n        }\n        var result: CFTypeRef?\n        let query: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: self.service,\n            kSecAttrAccount as String: self.account,\n            kSecMatchLimit as String: kSecMatchLimitOne,\n            kSecReturnData as String: true,\n        ]\n\n        if case .interactionRequired = KeychainAccessPreflight\n            .checkGenericPassword(service: self.service, account: self.account)\n        {\n            KeychainPromptHandler.handler?(KeychainPromptContext(\n                kind: .syntheticToken,\n                service: self.service,\n                account: self.account))\n        }\n\n        let status = SecItemCopyMatching(query as CFDictionary, &result)\n        if status == errSecItemNotFound {\n            return nil\n        }\n        guard status == errSecSuccess else {\n            Self.log.error(\"Keychain read failed: \\(status)\")\n            throw SyntheticTokenStoreError.keychainStatus(status)\n        }\n\n        guard let data = result as? Data else {\n            throw SyntheticTokenStoreError.invalidData\n        }\n        let token = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines)\n        if let token, !token.isEmpty {\n            return token\n        }\n        return nil\n    }\n\n    func storeToken(_ token: String?) throws {\n        guard !KeychainAccessGate.isDisabled else {\n            Self.log.debug(\"Keychain access disabled; skipping token store\")\n            return\n        }\n        let cleaned = token?.trimmingCharacters(in: .whitespacesAndNewlines)\n        if cleaned == nil || cleaned?.isEmpty == true {\n            try self.deleteTokenIfPresent()\n            return\n        }\n\n        let data = cleaned!.data(using: .utf8)!\n        let query: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: self.service,\n            kSecAttrAccount as String: self.account,\n        ]\n        let attributes: [String: Any] = [\n            kSecValueData as String: data,\n            kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,\n        ]\n\n        let updateStatus = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)\n        if updateStatus == errSecSuccess {\n            return\n        }\n        if updateStatus != errSecItemNotFound {\n            Self.log.error(\"Keychain update failed: \\(updateStatus)\")\n            throw SyntheticTokenStoreError.keychainStatus(updateStatus)\n        }\n\n        var addQuery = query\n        for (key, value) in attributes {\n            addQuery[key] = value\n        }\n        let addStatus = SecItemAdd(addQuery as CFDictionary, nil)\n        guard addStatus == errSecSuccess else {\n            Self.log.error(\"Keychain add failed: \\(addStatus)\")\n            throw SyntheticTokenStoreError.keychainStatus(addStatus)\n        }\n    }\n\n    private func deleteTokenIfPresent() throws {\n        guard !KeychainAccessGate.isDisabled else { return }\n        let query: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: self.service,\n            kSecAttrAccount as String: self.account,\n        ]\n        let status = SecItemDelete(query as CFDictionary)\n        if status == errSecSuccess || status == errSecItemNotFound {\n            return\n        }\n        Self.log.error(\"Keychain delete failed: \\(status)\")\n        throw SyntheticTokenStoreError.keychainStatus(status)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/UpdateChannel.swift",
    "content": "import Foundation\n\nenum UpdateChannel: String, CaseIterable, Codable {\n    case stable\n    case beta\n\n    static let userDefaultsKey = \"updateChannel\"\n    static let sparkleBetaChannel = \"beta\"\n\n    var displayName: String {\n        switch self {\n        case .stable:\n            \"Stable\"\n        case .beta:\n            \"Beta\"\n        }\n    }\n\n    var description: String {\n        switch self {\n        case .stable:\n            \"Receive only stable, production-ready releases.\"\n        case .beta:\n            \"Receive stable releases plus beta previews.\"\n        }\n    }\n\n    var allowedSparkleChannels: Set<String> {\n        switch self {\n        case .stable:\n            [\"\"]\n        case .beta:\n            [\"\", UpdateChannel.sparkleBetaChannel]\n        }\n    }\n\n    static var current: Self {\n        if let rawValue = UserDefaults.standard.string(forKey: userDefaultsKey),\n           let channel = Self(rawValue: rawValue)\n        {\n            return channel\n        }\n        return defaultChannel\n    }\n\n    static var defaultChannel: Self {\n        defaultChannel(for: Bundle.main.infoDictionary?[\"CFBundleShortVersionString\"] as? String ?? \"1.0\")\n    }\n\n    static func defaultChannel(for appVersion: String) -> Self {\n        if let isPrereleaseValue = Bundle.main.object(forInfoDictionaryKey: \"IS_PRERELEASE_BUILD\"),\n           let isPrerelease = isPrereleaseValue as? Bool,\n           isPrerelease\n        {\n            return .beta\n        }\n\n        let prereleaseKeywords = [\"beta\", \"alpha\", \"rc\", \"pre\", \"dev\"]\n        let lowercaseVersion = appVersion.lowercased()\n\n        for keyword in prereleaseKeywords where lowercaseVersion.contains(keyword) {\n            return .beta\n        }\n\n        return .stable\n    }\n}\n\nextension UpdateChannel: Identifiable {\n    var id: String {\n        rawValue\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/UsageBreakdownChartMenuView.swift",
    "content": "import Charts\nimport CodexBarCore\nimport SwiftUI\n\n@MainActor\nstruct UsageBreakdownChartMenuView: View {\n    private struct Point: Identifiable {\n        let id: String\n        let date: Date\n        let service: String\n        let creditsUsed: Double\n\n        init(date: Date, service: String, creditsUsed: Double) {\n            self.date = date\n            self.service = service\n            self.creditsUsed = creditsUsed\n            self.id = \"\\(service)-\\(Int(date.timeIntervalSince1970))- \\(creditsUsed)\"\n        }\n    }\n\n    private let breakdown: [OpenAIDashboardDailyBreakdown]\n    private let width: CGFloat\n    @State private var selectedDayKey: String?\n\n    init(breakdown: [OpenAIDashboardDailyBreakdown], width: CGFloat) {\n        self.breakdown = breakdown\n        self.width = width\n    }\n\n    var body: some View {\n        let model = Self.makeModel(from: self.breakdown)\n        VStack(alignment: .leading, spacing: 10) {\n            if model.points.isEmpty {\n                Text(\"No usage breakdown data.\")\n                    .font(.footnote)\n                    .foregroundStyle(.secondary)\n            } else {\n                Chart {\n                    ForEach(model.points) { point in\n                        BarMark(\n                            x: .value(\"Day\", point.date, unit: .day),\n                            y: .value(\"Credits used\", point.creditsUsed))\n                            .foregroundStyle(by: .value(\"Service\", point.service))\n                    }\n                    if let peak = model.peakPoint {\n                        let capStart = max(peak.creditsUsed - Self.capHeight(maxValue: model.maxCreditsUsed), 0)\n                        BarMark(\n                            x: .value(\"Day\", peak.date, unit: .day),\n                            yStart: .value(\"Cap start\", capStart),\n                            yEnd: .value(\"Cap end\", peak.creditsUsed))\n                            .foregroundStyle(Color(nsColor: .systemYellow))\n                    }\n                }\n                .chartForegroundStyleScale(domain: model.services, range: model.serviceColors)\n                .chartYAxis(.hidden)\n                .chartXAxis {\n                    AxisMarks(values: model.axisDates) { _ in\n                        AxisGridLine().foregroundStyle(Color.clear)\n                        AxisTick().foregroundStyle(Color.clear)\n                        AxisValueLabel(format: .dateTime.month(.abbreviated).day())\n                            .font(.caption2)\n                            .foregroundStyle(Color(nsColor: .tertiaryLabelColor))\n                    }\n                }\n                .chartLegend(.hidden)\n                .frame(height: 130)\n                .chartOverlay { proxy in\n                    GeometryReader { geo in\n                        ZStack(alignment: .topLeading) {\n                            if let rect = self.selectionBandRect(model: model, proxy: proxy, geo: geo) {\n                                Rectangle()\n                                    .fill(Self.selectionBandColor)\n                                    .frame(width: rect.width, height: rect.height)\n                                    .position(x: rect.midX, y: rect.midY)\n                                    .allowsHitTesting(false)\n                            }\n                            MouseLocationReader { location in\n                                self.updateSelection(location: location, model: model, proxy: proxy, geo: geo)\n                            }\n                            .frame(maxWidth: .infinity, maxHeight: .infinity)\n                            .contentShape(Rectangle())\n                        }\n                    }\n                }\n\n                let detail = self.detailLines(model: model)\n                VStack(alignment: .leading, spacing: 0) {\n                    Text(detail.primary)\n                        .font(.caption)\n                        .foregroundStyle(.secondary)\n                        .lineLimit(1)\n                        .truncationMode(.tail)\n                        .frame(height: 16, alignment: .leading)\n                    Text(detail.secondary ?? \" \")\n                        .font(.caption)\n                        .foregroundStyle(.secondary)\n                        .lineLimit(1)\n                        .truncationMode(.tail)\n                        .frame(height: 16, alignment: .leading)\n                        .opacity(detail.secondary == nil ? 0 : 1)\n                }\n\n                LazyVGrid(\n                    columns: [GridItem(.adaptive(minimum: 110), alignment: .leading)],\n                    alignment: .leading,\n                    spacing: 6)\n                {\n                    ForEach(model.services, id: \\.self) { service in\n                        HStack(spacing: 6) {\n                            Circle()\n                                .fill(model.color(for: service))\n                                .frame(width: 7, height: 7)\n                            Text(service)\n                                .font(.caption2)\n                                .foregroundStyle(.secondary)\n                                .lineLimit(1)\n                        }\n                    }\n                }\n            }\n        }\n        .padding(.horizontal, 16)\n        .padding(.vertical, 10)\n        .frame(minWidth: self.width, maxWidth: .infinity, alignment: .leading)\n    }\n\n    private struct Model {\n        let points: [Point]\n        let breakdownByDayKey: [String: OpenAIDashboardDailyBreakdown]\n        let dayDates: [(dayKey: String, date: Date)]\n        let selectableDayDates: [(dayKey: String, date: Date)]\n        let peakPoint: (date: Date, creditsUsed: Double)?\n        let services: [String]\n        let serviceColors: [Color]\n        let axisDates: [Date]\n        let maxCreditsUsed: Double\n\n        func color(for service: String) -> Color {\n            guard let idx = self.services.firstIndex(of: service), idx < self.serviceColors.count else {\n                return .secondary\n            }\n            return self.serviceColors[idx]\n        }\n    }\n\n    private static let selectionBandColor = Color(nsColor: .labelColor).opacity(0.1)\n\n    private static func makeModel(from breakdown: [OpenAIDashboardDailyBreakdown]) -> Model {\n        let sorted = breakdown\n            .sorted { lhs, rhs in lhs.day < rhs.day }\n\n        var points: [Point] = []\n        points.reserveCapacity(sorted.count * 2)\n\n        var breakdownByDayKey: [String: OpenAIDashboardDailyBreakdown] = [:]\n        breakdownByDayKey.reserveCapacity(sorted.count)\n\n        var dayDates: [(dayKey: String, date: Date)] = []\n        dayDates.reserveCapacity(sorted.count)\n\n        var selectableDayDates: [(dayKey: String, date: Date)] = []\n        selectableDayDates.reserveCapacity(sorted.count)\n\n        var peak: (date: Date, creditsUsed: Double)?\n        var maxCreditsUsed: Double = 0\n\n        for day in sorted {\n            guard let date = self.dateFromDayKey(day.day) else { continue }\n            breakdownByDayKey[day.day] = day\n            dayDates.append((dayKey: day.day, date: date))\n            if day.totalCreditsUsed > 0 {\n                if let cur = peak {\n                    if day.totalCreditsUsed > cur.creditsUsed { peak = (date, day.totalCreditsUsed) }\n                } else {\n                    peak = (date, day.totalCreditsUsed)\n                }\n                maxCreditsUsed = max(maxCreditsUsed, day.totalCreditsUsed)\n            }\n            var addedSelectable = false\n            for service in day.services where service.creditsUsed > 0 {\n                points.append(Point(date: date, service: service.service, creditsUsed: service.creditsUsed))\n                if !addedSelectable {\n                    selectableDayDates.append((dayKey: day.day, date: date))\n                    addedSelectable = true\n                }\n            }\n        }\n\n        let services = Self.serviceOrder(from: sorted)\n        let colors = services.map { Self.colorForService($0) }\n        let axisDates = Self.axisDates(fromSortedDays: sorted)\n\n        return Model(\n            points: points,\n            breakdownByDayKey: breakdownByDayKey,\n            dayDates: dayDates,\n            selectableDayDates: selectableDayDates,\n            peakPoint: peak,\n            services: services,\n            serviceColors: colors,\n            axisDates: axisDates,\n            maxCreditsUsed: maxCreditsUsed)\n    }\n\n    private static func capHeight(maxValue: Double) -> Double {\n        maxValue * 0.05\n    }\n\n    private static func serviceOrder(from breakdown: [OpenAIDashboardDailyBreakdown]) -> [String] {\n        var totals: [String: Double] = [:]\n        for day in breakdown {\n            for service in day.services {\n                totals[service.service, default: 0] += service.creditsUsed\n            }\n        }\n\n        return totals\n            .sorted { lhs, rhs in\n                if lhs.value == rhs.value { return lhs.key < rhs.key }\n                return lhs.value > rhs.value\n            }\n            .map(\\.key)\n    }\n\n    private static func colorForService(_ service: String) -> Color {\n        let lower = service.lowercased()\n        if lower == \"cli\" {\n            return Color(red: 0.26, green: 0.55, blue: 0.96)\n        }\n        if lower.contains(\"github\"), lower.contains(\"review\") {\n            return Color(red: 0.94, green: 0.53, blue: 0.18)\n        }\n        let palette: [Color] = [\n            Color(red: 0.46, green: 0.75, blue: 0.36),\n            Color(red: 0.80, green: 0.45, blue: 0.92),\n            Color(red: 0.26, green: 0.78, blue: 0.86),\n            Color(red: 0.94, green: 0.74, blue: 0.26),\n        ]\n        let idx = abs(service.hashValue) % palette.count\n        return palette[idx]\n    }\n\n    private static func axisDates(fromSortedDays sortedDays: [OpenAIDashboardDailyBreakdown]) -> [Date] {\n        guard let first = sortedDays.first, let last = sortedDays.last else { return [] }\n        guard let firstDate = self.dateFromDayKey(first.day),\n              let lastDate = self.dateFromDayKey(last.day)\n        else {\n            return []\n        }\n        if Calendar.current.isDate(firstDate, inSameDayAs: lastDate) {\n            return [firstDate]\n        }\n        return [firstDate, lastDate]\n    }\n\n    private static func dateFromDayKey(_ key: String) -> Date? {\n        let parts = key.split(separator: \"-\")\n        guard parts.count == 3,\n              let year = Int(parts[0]),\n              let month = Int(parts[1]),\n              let day = Int(parts[2])\n        else {\n            return nil\n        }\n\n        var comps = DateComponents()\n        comps.calendar = Calendar.current\n        comps.timeZone = TimeZone.current\n        comps.year = year\n        comps.month = month\n        comps.day = day\n        // Noon avoids off-by-one-day shifts if anything ends up interpreted in UTC.\n        comps.hour = 12\n        return comps.date\n    }\n\n    private func selectionBandRect(model: Model, proxy: ChartProxy, geo: GeometryProxy) -> CGRect? {\n        guard let key = self.selectedDayKey else { return nil }\n        guard let plotAnchor = proxy.plotFrame else { return nil }\n        let plotFrame = geo[plotAnchor]\n        guard let index = model.dayDates.firstIndex(where: { $0.dayKey == key }) else { return nil }\n        let date = model.dayDates[index].date\n        guard let x = proxy.position(forX: date) else { return nil }\n\n        func xForIndex(_ idx: Int) -> CGFloat? {\n            guard idx >= 0, idx < model.dayDates.count else { return nil }\n            return proxy.position(forX: model.dayDates[idx].date)\n        }\n\n        let xPrev = xForIndex(index - 1)\n        let xNext = xForIndex(index + 1)\n\n        if model.dayDates.count <= 1 {\n            return CGRect(\n                x: plotFrame.origin.x,\n                y: plotFrame.origin.y,\n                width: plotFrame.width,\n                height: plotFrame.height)\n        }\n\n        let leftInPlot: CGFloat = if let xPrev {\n            (xPrev + x) / 2\n        } else if let xNext {\n            x - (xNext - x) / 2\n        } else {\n            x - 8\n        }\n\n        let rightInPlot: CGFloat = if let xNext {\n            (xNext + x) / 2\n        } else if let xPrev {\n            x + (x - xPrev) / 2\n        } else {\n            x + 8\n        }\n\n        let left = plotFrame.origin.x + min(leftInPlot, rightInPlot)\n        let right = plotFrame.origin.x + max(leftInPlot, rightInPlot)\n        return CGRect(x: left, y: plotFrame.origin.y, width: right - left, height: plotFrame.height)\n    }\n\n    private func updateSelection(\n        location: CGPoint?,\n        model: Model,\n        proxy: ChartProxy,\n        geo: GeometryProxy)\n    {\n        guard let location else {\n            if self.selectedDayKey != nil { self.selectedDayKey = nil }\n            return\n        }\n\n        guard let plotAnchor = proxy.plotFrame else { return }\n        let plotFrame = geo[plotAnchor]\n        guard plotFrame.contains(location) else { return }\n\n        let xInPlot = location.x - plotFrame.origin.x\n        guard let date: Date = proxy.value(atX: xInPlot) else { return }\n        guard let nearest = self.nearestDayKey(to: date, model: model) else { return }\n\n        if self.selectedDayKey != nearest {\n            self.selectedDayKey = nearest\n        }\n    }\n\n    private func nearestDayKey(to date: Date, model: Model) -> String? {\n        guard !model.selectableDayDates.isEmpty else { return nil }\n        var best: (key: String, distance: TimeInterval)?\n        for entry in model.selectableDayDates {\n            let dist = abs(entry.date.timeIntervalSince(date))\n            if let cur = best {\n                if dist < cur.distance { best = (entry.dayKey, dist) }\n            } else {\n                best = (entry.dayKey, dist)\n            }\n        }\n        return best?.key\n    }\n\n    private func detailLines(model: Model) -> (primary: String, secondary: String?) {\n        guard let key = self.selectedDayKey,\n              let day = model.breakdownByDayKey[key],\n              let date = Self.dateFromDayKey(key)\n        else {\n            return (\"Hover a bar for details\", nil)\n        }\n\n        let dayLabel = date.formatted(.dateTime.month(.abbreviated).day())\n        let total = day.totalCreditsUsed.formatted(.number.precision(.fractionLength(0...2)))\n        if day.services.isEmpty {\n            return (\"\\(dayLabel): \\(total)\", nil)\n        }\n        if day.services.count <= 1, let first = day.services.first {\n            let used = first.creditsUsed.formatted(.number.precision(.fractionLength(0...2)))\n            return (\"\\(dayLabel): \\(used)\", first.service)\n        }\n\n        let services = day.services\n            .sorted { lhs, rhs in\n                if lhs.creditsUsed == rhs.creditsUsed { return lhs.service < rhs.service }\n                return lhs.creditsUsed > rhs.creditsUsed\n            }\n            .prefix(3)\n            .map { \"\\($0.service) \\($0.creditsUsed.formatted(.number.precision(.fractionLength(0...2))))\" }\n            .joined(separator: \" · \")\n\n        return (\"\\(dayLabel): \\(total)\", services)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/UsagePaceText.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nenum UsagePaceText {\n    struct WeeklyDetail {\n        let leftLabel: String\n        let rightLabel: String?\n        let expectedUsedPercent: Double\n        let stage: UsagePace.Stage\n    }\n\n    static func weeklySummary(pace: UsagePace, now: Date = .init()) -> String {\n        let detail = self.weeklyDetail(pace: pace, now: now)\n        if let rightLabel = detail.rightLabel {\n            return \"Pace: \\(detail.leftLabel) · \\(rightLabel)\"\n        }\n        return \"Pace: \\(detail.leftLabel)\"\n    }\n\n    static func weeklyDetail(pace: UsagePace, now: Date = .init()) -> WeeklyDetail {\n        WeeklyDetail(\n            leftLabel: self.detailLeftLabel(for: pace),\n            rightLabel: self.detailRightLabel(for: pace, now: now),\n            expectedUsedPercent: pace.expectedUsedPercent,\n            stage: pace.stage)\n    }\n\n    private static func detailLeftLabel(for pace: UsagePace) -> String {\n        let deltaValue = Int(abs(pace.deltaPercent).rounded())\n        switch pace.stage {\n        case .onTrack:\n            return \"On pace\"\n        case .slightlyAhead, .ahead, .farAhead:\n            return \"\\(deltaValue)% in deficit\"\n        case .slightlyBehind, .behind, .farBehind:\n            return \"\\(deltaValue)% in reserve\"\n        }\n    }\n\n    private static func detailRightLabel(for pace: UsagePace, now: Date) -> String? {\n        let etaLabel: String?\n        if pace.willLastToReset {\n            etaLabel = \"Lasts until reset\"\n        } else if let etaSeconds = pace.etaSeconds {\n            let etaText = Self.durationText(seconds: etaSeconds, now: now)\n            etaLabel = etaText == \"now\" ? \"Runs out now\" : \"Runs out in \\(etaText)\"\n        } else {\n            etaLabel = nil\n        }\n\n        guard let runOutProbability = pace.runOutProbability else { return etaLabel }\n        let roundedRisk = self.roundedRiskPercent(runOutProbability)\n        let riskLabel = \"≈ \\(roundedRisk)% run-out risk\"\n        if let etaLabel {\n            return \"\\(etaLabel) · \\(riskLabel)\"\n        }\n        return riskLabel\n    }\n\n    private static func durationText(seconds: TimeInterval, now: Date) -> String {\n        let date = now.addingTimeInterval(seconds)\n        let countdown = UsageFormatter.resetCountdownDescription(from: date, now: now)\n        if countdown == \"now\" { return \"now\" }\n        if countdown.hasPrefix(\"in \") { return String(countdown.dropFirst(3)) }\n        return countdown\n    }\n\n    private static func roundedRiskPercent(_ probability: Double) -> Int {\n        let percent = probability.clamped(to: 0...1) * 100\n        let rounded = (percent / 5).rounded() * 5\n        return Int(rounded)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/UsageProgressBar.swift",
    "content": "import SwiftUI\n\n/// Static progress fill with no implicit animations, used inside the menu card.\nstruct UsageProgressBar: View {\n    private static let paceStripeCount = 3\n    private static func paceStripeWidth(for scale: CGFloat) -> CGFloat {\n        2\n    }\n\n    private static func paceStripeSpan(for scale: CGFloat) -> CGFloat {\n        let stripeCount = max(1, Self.paceStripeCount)\n        return Self.paceStripeWidth(for: scale) * CGFloat(stripeCount)\n    }\n\n    let percent: Double\n    let tint: Color\n    let accessibilityLabel: String\n    let pacePercent: Double?\n    let paceOnTop: Bool\n    @Environment(\\.menuItemHighlighted) private var isHighlighted\n    @Environment(\\.displayScale) private var displayScale\n\n    init(\n        percent: Double,\n        tint: Color,\n        accessibilityLabel: String,\n        pacePercent: Double? = nil,\n        paceOnTop: Bool = true)\n    {\n        self.percent = percent\n        self.tint = tint\n        self.accessibilityLabel = accessibilityLabel\n        self.pacePercent = pacePercent\n        self.paceOnTop = paceOnTop\n    }\n\n    private var clamped: Double {\n        min(100, max(0, self.percent))\n    }\n\n    var body: some View {\n        GeometryReader { proxy in\n            let scale = max(self.displayScale, 1)\n            let fillWidth = proxy.size.width * self.clamped / 100\n            let paceWidth = proxy.size.width * Self.clampedPercent(self.pacePercent) / 100\n            let tipWidth = max(25, proxy.size.height * 6.5)\n            let stripeInset = 1 / scale\n            let tipOffset = paceWidth - tipWidth + (Self.paceStripeSpan(for: scale) / 2) + stripeInset\n            let showTip = self.pacePercent != nil && tipWidth > 0.5\n            let needsPunchCompositing = showTip\n            let bar = ZStack(alignment: .leading) {\n                Capsule()\n                    .fill(MenuHighlightStyle.progressTrack(self.isHighlighted))\n                self.actualBar(width: fillWidth)\n                if showTip {\n                    self.paceTip(width: tipWidth)\n                        .offset(x: tipOffset)\n                }\n            }\n            .clipped()\n            if self.isHighlighted {\n                bar\n                    .compositingGroup()\n                    .drawingGroup()\n            } else if needsPunchCompositing {\n                bar\n                    .compositingGroup()\n            } else {\n                bar\n            }\n        }\n        .frame(height: 6)\n        .accessibilityLabel(self.accessibilityLabel)\n        .accessibilityValue(\"\\(Int(self.clamped)) percent\")\n    }\n\n    private func actualBar(width: CGFloat) -> some View {\n        Capsule()\n            .fill(MenuHighlightStyle.progressTint(self.isHighlighted, fallback: self.tint))\n            .frame(width: width)\n            .contentShape(Rectangle())\n            .allowsHitTesting(false)\n    }\n\n    private func paceTip(width: CGFloat) -> some View {\n        let isDeficit = self.paceOnTop == false\n        let useDeficitRed = isDeficit && self.isHighlighted == false\n        return GeometryReader { proxy in\n            let size = proxy.size\n            let rect = CGRect(origin: .zero, size: size)\n            let scale = max(self.displayScale, 1)\n            let stripes = Self.paceStripePaths(size: size, scale: scale)\n            let stripeColor: Color = if self.isHighlighted {\n                .white\n            } else if useDeficitRed {\n                .red\n            } else {\n                .green\n            }\n\n            ZStack {\n                Canvas { context, _ in\n                    context.clip(to: Path(rect))\n                    context.fill(stripes.punched, with: .color(.white.opacity(0.9)))\n                }\n                .blendMode(.destinationOut)\n\n                Canvas { context, _ in\n                    context.clip(to: Path(rect))\n                    context.fill(stripes.center, with: .color(stripeColor))\n                }\n            }\n        }\n        .frame(width: width)\n        .contentShape(Rectangle())\n        .allowsHitTesting(false)\n    }\n\n    private static func paceStripePaths(size: CGSize, scale: CGFloat) -> (punched: Path, center: Path) {\n        let rect = CGRect(origin: .zero, size: size)\n        let extend = size.height * 2\n        let stripeTopY: CGFloat = -extend\n        let stripeBottomY: CGFloat = size.height + extend\n        let align: (CGFloat) -> CGFloat = { value in\n            (value * scale).rounded() / scale\n        }\n\n        let stripeWidth = Self.paceStripeWidth(for: scale)\n        let punchWidth = stripeWidth * 3\n        let stripeInset = 1 / scale\n        let stripeAnchorX = align(rect.maxX - stripeInset)\n        let stripeMinY = align(stripeTopY)\n        let stripeMaxY = align(stripeBottomY)\n        let anchorTopX = stripeAnchorX\n        var punchedStripe = Path()\n        var centerStripe = Path()\n        let availableWidth = (anchorTopX - punchWidth) - rect.minX\n        guard availableWidth >= 0 else { return (punchedStripe, centerStripe) }\n\n        let punchRightTopX = align(anchorTopX)\n        let punchLeftTopX = punchRightTopX - punchWidth\n        let punchRightBottomX = punchRightTopX\n        let punchLeftBottomX = punchLeftTopX\n        punchedStripe.addPath(Path { path in\n            path.move(to: CGPoint(x: punchLeftTopX, y: stripeMinY))\n            path.addLine(to: CGPoint(x: punchRightTopX, y: stripeMinY))\n            path.addLine(to: CGPoint(x: punchRightBottomX, y: stripeMaxY))\n            path.addLine(to: CGPoint(x: punchLeftBottomX, y: stripeMaxY))\n            path.closeSubpath()\n        })\n\n        let centerLeftTopX = align(punchLeftTopX + (punchWidth - stripeWidth) / 2)\n        let centerRightTopX = centerLeftTopX + stripeWidth\n        let centerRightBottomX = centerRightTopX\n        let centerLeftBottomX = centerLeftTopX\n        centerStripe.addPath(Path { path in\n            path.move(to: CGPoint(x: centerLeftTopX, y: stripeMinY))\n            path.addLine(to: CGPoint(x: centerRightTopX, y: stripeMinY))\n            path.addLine(to: CGPoint(x: centerRightBottomX, y: stripeMaxY))\n            path.addLine(to: CGPoint(x: centerLeftBottomX, y: stripeMaxY))\n            path.closeSubpath()\n        })\n\n        return (punchedStripe, centerStripe)\n    }\n\n    private static func clampedPercent(_ value: Double?) -> Double {\n        guard let value else { return 0 }\n        return min(100, max(0, value))\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/UsageStore+Accessors.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nextension UsageStore {\n    var codexSnapshot: UsageSnapshot? {\n        self.snapshots[.codex]\n    }\n\n    var claudeSnapshot: UsageSnapshot? {\n        self.snapshots[.claude]\n    }\n\n    var lastCodexError: String? {\n        self.errors[.codex]\n    }\n\n    var lastClaudeError: String? {\n        self.errors[.claude]\n    }\n\n    func error(for provider: UsageProvider) -> String? {\n        self.errors[provider]\n    }\n\n    func status(for provider: UsageProvider) -> ProviderStatus? {\n        guard self.statusChecksEnabled else { return nil }\n        return self.statuses[provider]\n    }\n\n    func statusIndicator(for provider: UsageProvider) -> ProviderStatusIndicator {\n        self.status(for: provider)?.indicator ?? .none\n    }\n\n    func accountInfo() -> AccountInfo {\n        self.codexFetcher.loadAccountInfo()\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/UsageStore+ClaudeDebug.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport SweetCookieKit\n\n@MainActor\nextension UsageStore {\n    func debugClaudeDump() async -> String {\n        await ClaudeStatusProbe.latestDumps()\n    }\n}\n\nextension UsageStore {\n    struct ClaudeDebugLogConfiguration: Sendable {\n        let runtime: CodexBarCore.ProviderRuntime\n        let sourceMode: ProviderSourceMode\n        let environment: [String: String]\n        let webExtrasEnabled: Bool\n        let usageDataSource: ClaudeUsageDataSource\n        let cookieSource: ProviderCookieSource\n        let cookieHeader: String\n        let keepCLISessionsAlive: Bool\n    }\n\n    static func debugClaudeLog(\n        browserDetection: BrowserDetection,\n        configuration: ClaudeDebugLogConfiguration) async -> String\n    {\n        struct OAuthDebugProbe: Sendable {\n            let hasCredentials: Bool\n            let ownerRawValue: String\n            let sourceRawValue: String\n            let isExpired: Bool\n        }\n\n        return await runWithTimeout(seconds: 15) {\n            var lines: [String] = []\n            let manualHeader = configuration.cookieSource == .manual\n                ? CookieHeaderNormalizer.normalize(configuration.cookieHeader)\n                : nil\n            let hasKey = if configuration.cookieSource == .off {\n                false\n            } else if let manualHeader {\n                ClaudeWebAPIFetcher.hasSessionKey(cookieHeader: manualHeader)\n            } else {\n                ClaudeWebAPIFetcher.hasSessionKey(browserDetection: browserDetection) { msg in lines.append(msg) }\n            }\n            let oauthProbe = await withTaskGroup(of: OAuthDebugProbe.self) { group in\n                // Preserve task-local test overrides while keeping the keychain read off the calling task.\n                group.addTask(priority: .utility) {\n                    let oauthRecord = try? ClaudeOAuthCredentialsStore.loadRecord(\n                        environment: configuration.environment,\n                        allowKeychainPrompt: false,\n                        respectKeychainPromptCooldown: true,\n                        allowClaudeKeychainRepairWithoutPrompt: false)\n                    return OAuthDebugProbe(\n                        hasCredentials: oauthRecord?.credentials.scopes.contains(\"user:profile\") == true,\n                        ownerRawValue: oauthRecord?.owner.rawValue ?? \"none\",\n                        sourceRawValue: oauthRecord?.source.rawValue ?? \"none\",\n                        isExpired: oauthRecord?.credentials.isExpired ?? false)\n                }\n                return await group.next() ?? OAuthDebugProbe(\n                    hasCredentials: false,\n                    ownerRawValue: \"none\",\n                    sourceRawValue: \"none\",\n                    isExpired: false)\n            }\n            let hasOAuthCredentials = ClaudeOAuthPlanningAvailability.isAvailable(\n                runtime: configuration.runtime,\n                sourceMode: configuration.sourceMode,\n                environment: configuration.environment)\n            let hasClaudeBinary = ClaudeCLIResolver.isAvailable(environment: configuration.environment)\n            let delegatedCooldownSeconds = ClaudeOAuthDelegatedRefreshCoordinator.cooldownRemainingSeconds()\n            let planningInput = ClaudeSourcePlanningInput(\n                runtime: configuration.runtime,\n                selectedDataSource: configuration.usageDataSource,\n                webExtrasEnabled: configuration.webExtrasEnabled,\n                hasWebSession: hasKey,\n                hasCLI: hasClaudeBinary,\n                hasOAuthCredentials: hasOAuthCredentials)\n            let plan = ClaudeSourcePlanner.resolve(input: planningInput)\n            let strategy = plan.compatibilityStrategy\n\n            lines.append(contentsOf: plan.debugLines())\n            lines.append(\"hasSessionKey=\\(hasKey)\")\n            lines.append(\"hasOAuthCredentials=\\(hasOAuthCredentials)\")\n            lines.append(\"oauthCredentialOwner=\\(oauthProbe.ownerRawValue)\")\n            lines.append(\"oauthCredentialSource=\\(oauthProbe.sourceRawValue)\")\n            lines.append(\"oauthCredentialExpired=\\(oauthProbe.isExpired)\")\n            lines.append(\"delegatedRefreshCLIAvailable=\\(hasClaudeBinary)\")\n            lines.append(\"delegatedRefreshCooldownActive=\\(delegatedCooldownSeconds != nil)\")\n            if let delegatedCooldownSeconds {\n                lines.append(\"delegatedRefreshCooldownSeconds=\\(delegatedCooldownSeconds)\")\n            }\n            lines.append(\"hasClaudeBinary=\\(hasClaudeBinary)\")\n            if strategy?.useWebExtras == true {\n                lines.append(\"web_extras=enabled\")\n            }\n            lines.append(\"\")\n\n            guard let strategy else {\n                lines.append(\"No planner-selected Claude source.\")\n                return lines.joined(separator: \"\\n\")\n            }\n\n            switch strategy.dataSource {\n            case .auto:\n                lines.append(\"Auto source selected.\")\n                return lines.joined(separator: \"\\n\")\n            case .web:\n                do {\n                    let web: ClaudeWebAPIFetcher.WebUsageData =\n                        if let manualHeader {\n                            try await ClaudeWebAPIFetcher.fetchUsage(cookieHeader: manualHeader) { msg in\n                                lines.append(msg)\n                            }\n                        } else {\n                            try await ClaudeWebAPIFetcher.fetchUsage(browserDetection: browserDetection) { msg in\n                                lines.append(msg)\n                            }\n                        }\n                    lines.append(\"\")\n                    lines.append(\"Web API summary:\")\n\n                    let sessionReset = web.sessionResetsAt?.description ?? \"nil\"\n                    lines.append(\"session_used=\\(web.sessionPercentUsed)% resetsAt=\\(sessionReset)\")\n\n                    if let weekly = web.weeklyPercentUsed {\n                        let weeklyReset = web.weeklyResetsAt?.description ?? \"nil\"\n                        lines.append(\"weekly_used=\\(weekly)% resetsAt=\\(weeklyReset)\")\n                    } else {\n                        lines.append(\"weekly_used=nil\")\n                    }\n\n                    lines.append(\"opus_used=\\(web.opusPercentUsed?.description ?? \"nil\")\")\n\n                    if let extra = web.extraUsageCost {\n                        let resetsAt = extra.resetsAt?.description ?? \"nil\"\n                        let period = extra.period ?? \"nil\"\n                        let line =\n                            \"extra_usage used=\\(extra.used) limit=\\(extra.limit) \" +\n                            \"currency=\\(extra.currencyCode) period=\\(period) resetsAt=\\(resetsAt)\"\n                        lines.append(line)\n                    } else {\n                        lines.append(\"extra_usage=nil\")\n                    }\n\n                    return lines.joined(separator: \"\\n\")\n                } catch {\n                    lines.append(\"Web API failed: \\(error.localizedDescription)\")\n                    return lines.joined(separator: \"\\n\")\n                }\n            case .cli:\n                let fetcher = ClaudeUsageFetcher(\n                    browserDetection: browserDetection,\n                    environment: configuration.environment,\n                    runtime: configuration.runtime,\n                    dataSource: configuration.usageDataSource,\n                    keepCLISessionsAlive: configuration.keepCLISessionsAlive)\n                let cli = await fetcher.debugRawProbe(model: \"sonnet\")\n                lines.append(cli)\n                return lines.joined(separator: \"\\n\")\n            case .oauth:\n                lines.append(\"OAuth source selected.\")\n                return lines.joined(separator: \"\\n\")\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/UsageStore+HighestUsage.swift",
    "content": "import CodexBarCore\nimport Foundation\n\n@MainActor\nextension UsageStore {\n    /// Returns the enabled provider with the highest usage percentage (closest to rate limit).\n    /// Excludes providers that are fully rate-limited.\n    func providerWithHighestUsage() -> (provider: UsageProvider, usedPercent: Double)? {\n        var highest: (provider: UsageProvider, usedPercent: Double)?\n        for provider in self.enabledProviders() {\n            guard let snapshot = self.snapshots[provider] else { continue }\n            let window = self.menuBarMetricWindowForHighestUsage(provider: provider, snapshot: snapshot)\n            let percent = window?.usedPercent ?? 0\n            guard !self.shouldExcludeFromHighestUsage(\n                provider: provider,\n                snapshot: snapshot,\n                metricPercent: percent)\n            else {\n                continue\n            }\n            if highest == nil || percent > highest!.usedPercent {\n                highest = (provider, percent)\n            }\n        }\n        return highest\n    }\n\n    private func menuBarMetricWindowForHighestUsage(provider: UsageProvider, snapshot: UsageSnapshot) -> RateWindow? {\n        switch self.settings.menuBarMetricPreference(for: provider) {\n        case .primary:\n            return snapshot.primary ?? snapshot.secondary\n        case .secondary:\n            return snapshot.secondary ?? snapshot.primary\n        case .average:\n            guard let primary = snapshot.primary, let secondary = snapshot.secondary else {\n                return snapshot.primary ?? snapshot.secondary\n            }\n            let usedPercent = (primary.usedPercent + secondary.usedPercent) / 2\n            return RateWindow(usedPercent: usedPercent, windowMinutes: nil, resetsAt: nil, resetDescription: nil)\n        case .automatic:\n            if provider == .factory || provider == .kimi {\n                return snapshot.secondary ?? snapshot.primary\n            }\n            if provider == .copilot,\n               let primary = snapshot.primary,\n               let secondary = snapshot.secondary\n            {\n                // Copilot can expose chat + completions quotas; rank by the more constrained one.\n                return primary.usedPercent >= secondary.usedPercent ? primary : secondary\n            }\n            return snapshot.primary ?? snapshot.secondary\n        }\n    }\n\n    private func shouldExcludeFromHighestUsage(\n        provider: UsageProvider,\n        snapshot: UsageSnapshot,\n        metricPercent: Double)\n        -> Bool\n    {\n        guard metricPercent >= 100 else { return false }\n        if provider == .copilot,\n           self.settings.menuBarMetricPreference(for: provider) == .automatic,\n           let primary = snapshot.primary,\n           let secondary = snapshot.secondary\n        {\n            // In automatic mode Copilot can have one depleted lane while another still has quota.\n            return primary.usedPercent >= 100 && secondary.usedPercent >= 100\n        }\n        return true\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/UsageStore+HistoricalPace.swift",
    "content": "import CodexBarCore\nimport CryptoKit\nimport Foundation\n\n@MainActor\nextension UsageStore {\n    private static let minimumPaceExpectedPercent: Double = 3\n    private static let backfillMaxTimestampMismatch: TimeInterval = 5 * 60\n\n    func weeklyPace(provider: UsageProvider, window: RateWindow, now: Date = .init()) -> UsagePace? {\n        guard provider == .codex || provider == .claude else { return nil }\n        guard window.remainingPercent > 0 else { return nil }\n        let resolved: UsagePace?\n        if provider == .codex, self.settings.historicalTrackingEnabled {\n            let codexAccountKey = self.codexHistoricalAccountKey()\n            if self.codexHistoricalDatasetAccountKey == codexAccountKey,\n               let historical = CodexHistoricalPaceEvaluator.evaluate(\n                   window: window,\n                   now: now,\n                   dataset: self.codexHistoricalDataset)\n            {\n                resolved = historical\n            } else {\n                resolved = UsagePace.weekly(window: window, now: now, defaultWindowMinutes: 10080)\n            }\n        } else {\n            resolved = UsagePace.weekly(window: window, now: now, defaultWindowMinutes: 10080)\n        }\n\n        guard let resolved else { return nil }\n        guard resolved.expectedUsedPercent >= Self.minimumPaceExpectedPercent else { return nil }\n        return resolved\n    }\n\n    func recordCodexHistoricalSampleIfNeeded(snapshot: UsageSnapshot) {\n        guard self.settings.historicalTrackingEnabled else { return }\n        guard let weekly = snapshot.secondary else { return }\n\n        let sampledAt = snapshot.updatedAt\n        let accountKey = self.codexHistoricalAccountKey(preferredEmail: snapshot.accountEmail(for: .codex))\n        let historyStore = self.historicalUsageHistoryStore\n        Task.detached(priority: .utility) { [weak self] in\n            let dataset = await historyStore.recordCodexWeekly(\n                window: weekly,\n                sampledAt: sampledAt,\n                accountKey: accountKey)\n            await MainActor.run { [weak self] in\n                self?.setCodexHistoricalDataset(dataset, accountKey: accountKey)\n            }\n        }\n    }\n\n    func refreshHistoricalDatasetIfNeeded() async {\n        if !self.settings.historicalTrackingEnabled {\n            self.setCodexHistoricalDataset(nil, accountKey: nil)\n            return\n        }\n        let accountKey = self.codexHistoricalAccountKey(dashboard: self.openAIDashboard)\n        let dataset = await self.historicalUsageHistoryStore.loadCodexDataset(accountKey: accountKey)\n        self.setCodexHistoricalDataset(dataset, accountKey: accountKey)\n        if let dashboard = self.openAIDashboard {\n            self.backfillCodexHistoricalFromDashboardIfNeeded(dashboard)\n        }\n    }\n\n    func backfillCodexHistoricalFromDashboardIfNeeded(_ dashboard: OpenAIDashboardSnapshot) {\n        guard self.settings.historicalTrackingEnabled else { return }\n        guard !dashboard.usageBreakdown.isEmpty else { return }\n\n        let codexSnapshot = self.snapshots[.codex]\n        let accountKey = self.codexHistoricalAccountKey(\n            preferredEmail: codexSnapshot?.accountEmail(for: .codex),\n            dashboard: dashboard)\n        let referenceWindow: RateWindow\n        let calibrationAt: Date\n        if let dashboardWeekly = dashboard.secondaryLimit {\n            referenceWindow = dashboardWeekly\n            calibrationAt = dashboard.updatedAt\n        } else if let codexSnapshot, let snapshotWeekly = codexSnapshot.secondary {\n            let mismatch = abs(codexSnapshot.updatedAt.timeIntervalSince(dashboard.updatedAt))\n            guard mismatch <= Self.backfillMaxTimestampMismatch else { return }\n            referenceWindow = snapshotWeekly\n            calibrationAt = min(codexSnapshot.updatedAt, dashboard.updatedAt)\n        } else {\n            return\n        }\n\n        let historyStore = self.historicalUsageHistoryStore\n        let usageBreakdown = dashboard.usageBreakdown\n        Task.detached(priority: .utility) { [weak self] in\n            let dataset = await historyStore.backfillCodexWeeklyFromUsageBreakdown(\n                usageBreakdown,\n                referenceWindow: referenceWindow,\n                now: calibrationAt,\n                accountKey: accountKey)\n            await MainActor.run { [weak self] in\n                self?.setCodexHistoricalDataset(dataset, accountKey: accountKey)\n            }\n        }\n    }\n\n    private func setCodexHistoricalDataset(_ dataset: CodexHistoricalDataset?, accountKey: String?) {\n        self.codexHistoricalDataset = dataset\n        self.codexHistoricalDatasetAccountKey = accountKey\n        self.historicalPaceRevision += 1\n    }\n\n    private func codexHistoricalAccountKey(\n        preferredEmail: String? = nil,\n        dashboard: OpenAIDashboardSnapshot? = nil) -> String?\n    {\n        let sourceEmail = preferredEmail ??\n            self.snapshots[.codex]?.accountEmail(for: .codex) ??\n            dashboard?.signedInEmail ??\n            self.codexAccountEmailForOpenAIDashboard()\n        guard let sourceEmail else { return nil }\n        let normalized = sourceEmail\n            .trimmingCharacters(in: .whitespacesAndNewlines)\n            .lowercased()\n        guard !normalized.isEmpty else { return nil }\n        return Self.sha256Hex(normalized)\n    }\n\n    private static func sha256Hex(_ input: String) -> String {\n        let digest = SHA256.hash(data: Data(input.utf8))\n        return digest.map { String(format: \"%02x\", $0) }.joined()\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/UsageStore+Logging.swift",
    "content": "import CodexBarCore\n\nextension UsageStore {\n    func logStartupState() {\n        let modeSnapshot: [String: String] = [\n            \"codexUsageSource\": self.settings.codexUsageDataSource.rawValue,\n            \"claudeUsageSource\": self.settings.claudeUsageDataSource.rawValue,\n            \"kiloUsageSource\": self.settings.kiloUsageDataSource.rawValue,\n            \"codexCookieSource\": self.settings.codexCookieSource.rawValue,\n            \"claudeCookieSource\": self.settings.claudeCookieSource.rawValue,\n            \"cursorCookieSource\": self.settings.cursorCookieSource.rawValue,\n            \"opencodeCookieSource\": self.settings.opencodeCookieSource.rawValue,\n            \"factoryCookieSource\": self.settings.factoryCookieSource.rawValue,\n            \"minimaxCookieSource\": self.settings.minimaxCookieSource.rawValue,\n            \"kimiCookieSource\": self.settings.kimiCookieSource.rawValue,\n            \"augmentCookieSource\": self.settings.augmentCookieSource.rawValue,\n            \"ampCookieSource\": self.settings.ampCookieSource.rawValue,\n            \"ollamaCookieSource\": self.settings.ollamaCookieSource.rawValue,\n            \"openAIWebAccess\": self.settings.openAIWebAccessEnabled ? \"1\" : \"0\",\n            \"claudeWebExtras\": self.settings.claudeWebExtrasEnabled ? \"1\" : \"0\",\n            \"kiloExtras\": self.settings.kiloExtrasEnabled ? \"1\" : \"0\",\n        ]\n        ProviderLogging.logStartupState(\n            logger: self.providerLogger,\n            providers: Array(self.providerMetadata.keys),\n            isEnabled: { provider in\n                self.settings.isProviderEnabled(\n                    provider: provider,\n                    metadata: self.providerMetadata[provider]!)\n            },\n            modeSnapshot: modeSnapshot)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/UsageStore+OpenAIWeb.swift",
    "content": "import Foundation\n\n// MARK: - OpenAI web error messaging\n\nextension UsageStore {\n    func openAIDashboardFriendlyError(\n        body: String,\n        targetEmail: String?,\n        cookieImportStatus: String?) -> String?\n    {\n        let trimmed = body.trimmingCharacters(in: .whitespacesAndNewlines)\n        let status = cookieImportStatus?.trimmingCharacters(in: .whitespacesAndNewlines)\n        if trimmed.isEmpty {\n            return [\n                \"OpenAI web dashboard returned an empty page.\",\n                \"Sign in to chatgpt.com and update OpenAI cookies in Providers → Codex.\",\n            ].joined(separator: \" \")\n        }\n\n        let lower = trimmed.lowercased()\n        let looksLikePublicLanding = lower.contains(\"skip to content\")\n            && (lower.contains(\"about\") || lower.contains(\"openai\") || lower.contains(\"chatgpt\"))\n        let looksLoggedOut = lower.contains(\"sign in\")\n            || lower.contains(\"log in\")\n            || lower.contains(\"create account\")\n            || lower.contains(\"continue with google\")\n            || lower.contains(\"continue with apple\")\n            || lower.contains(\"continue with microsoft\")\n\n        guard looksLikePublicLanding || looksLoggedOut else { return nil }\n        let emailLabel = targetEmail?.trimmingCharacters(in: .whitespacesAndNewlines)\n        let targetLabel = (emailLabel?.isEmpty == false) ? emailLabel! : \"your OpenAI account\"\n        if let status, !status.isEmpty {\n            if status.contains(\"cookies do not match Codex account\")\n                || status.localizedCaseInsensitiveContains(\"cookie import failed\")\n            {\n                return [\n                    status,\n                    \"Sign in to chatgpt.com as \\(targetLabel), then update OpenAI cookies in Providers → Codex.\",\n                ].joined(separator: \" \")\n            }\n        }\n        return [\n            \"OpenAI web dashboard returned a public page (not signed in).\",\n            \"Sign in to chatgpt.com as \\(targetLabel), then update OpenAI cookies in Providers → Codex.\",\n        ].joined(separator: \" \")\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/UsageStore+Refresh.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nextension UsageStore {\n    /// Force refresh Augment session (called from UI button)\n    func forceRefreshAugmentSession() async {\n        await self.performRuntimeAction(.forceSessionRefresh, for: .augment)\n    }\n\n    func refreshProvider(_ provider: UsageProvider, allowDisabled: Bool = false) async {\n        guard let spec = self.providerSpecs[provider] else { return }\n\n        if !spec.isEnabled(), !allowDisabled {\n            self.refreshingProviders.remove(provider)\n            await MainActor.run {\n                self.snapshots.removeValue(forKey: provider)\n                self.errors[provider] = nil\n                self.lastSourceLabels.removeValue(forKey: provider)\n                self.lastFetchAttempts.removeValue(forKey: provider)\n                self.accountSnapshots.removeValue(forKey: provider)\n                self.tokenSnapshots.removeValue(forKey: provider)\n                self.tokenErrors[provider] = nil\n                self.failureGates[provider]?.reset()\n                self.tokenFailureGates[provider]?.reset()\n                self.statuses.removeValue(forKey: provider)\n                self.lastKnownSessionRemaining.removeValue(forKey: provider)\n                self.lastKnownSessionWindowSource.removeValue(forKey: provider)\n                self.lastTokenFetchAt.removeValue(forKey: provider)\n            }\n            return\n        }\n\n        self.refreshingProviders.insert(provider)\n        defer { self.refreshingProviders.remove(provider) }\n\n        let tokenAccounts = self.tokenAccounts(for: provider)\n        if self.shouldFetchAllTokenAccounts(provider: provider, accounts: tokenAccounts) {\n            await self.refreshTokenAccounts(provider: provider, accounts: tokenAccounts)\n            return\n        } else {\n            _ = await MainActor.run {\n                self.accountSnapshots.removeValue(forKey: provider)\n            }\n        }\n\n        let fetchContext = spec.makeFetchContext()\n        let descriptor = spec.descriptor\n        // Keep provider fetch work off MainActor so slow keychain/process reads don't stall menu/UI responsiveness.\n        let outcome = await withTaskGroup(\n            of: ProviderFetchOutcome.self,\n            returning: ProviderFetchOutcome.self)\n        { group in\n            group.addTask {\n                await descriptor.fetchOutcome(context: fetchContext)\n            }\n            return await group.next()!\n        }\n        if provider == .claude,\n           ClaudeOAuthCredentialsStore.invalidateCacheIfCredentialsFileChanged()\n        {\n            await MainActor.run {\n                self.snapshots.removeValue(forKey: .claude)\n                self.errors[.claude] = nil\n                self.lastSourceLabels.removeValue(forKey: .claude)\n                self.lastFetchAttempts.removeValue(forKey: .claude)\n                self.accountSnapshots.removeValue(forKey: .claude)\n                self.tokenSnapshots.removeValue(forKey: .claude)\n                self.tokenErrors[.claude] = nil\n                self.failureGates[.claude]?.reset()\n                self.tokenFailureGates[.claude]?.reset()\n                self.lastTokenFetchAt.removeValue(forKey: .claude)\n            }\n        }\n        await MainActor.run {\n            self.lastFetchAttempts[provider] = outcome.attempts\n        }\n\n        switch outcome.result {\n        case let .success(result):\n            let scoped = result.usage.scoped(to: provider)\n            await MainActor.run {\n                self.handleSessionQuotaTransition(provider: provider, snapshot: scoped)\n                self.snapshots[provider] = scoped\n                self.lastSourceLabels[provider] = result.sourceLabel\n                self.errors[provider] = nil\n                self.failureGates[provider]?.recordSuccess()\n            }\n            if let runtime = self.providerRuntimes[provider] {\n                let context = ProviderRuntimeContext(\n                    provider: provider, settings: self.settings, store: self)\n                runtime.providerDidRefresh(context: context, provider: provider)\n            }\n            if provider == .codex {\n                self.recordCodexHistoricalSampleIfNeeded(snapshot: scoped)\n            }\n        case let .failure(error):\n            await MainActor.run {\n                let hadPriorData = self.snapshots[provider] != nil\n                let shouldSurface =\n                    self.failureGates[provider]?\n                        .shouldSurfaceError(onFailureWithPriorData: hadPriorData) ?? true\n                if shouldSurface {\n                    self.errors[provider] = error.localizedDescription\n                    self.snapshots.removeValue(forKey: provider)\n                } else {\n                    self.errors[provider] = nil\n                }\n            }\n            if let runtime = self.providerRuntimes[provider] {\n                let context = ProviderRuntimeContext(\n                    provider: provider, settings: self.settings, store: self)\n                runtime.providerDidFail(context: context, provider: provider, error: error)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/UsageStore+Status.swift",
    "content": "import Foundation\n\nextension UsageStore {\n    static func fetchStatus(from baseURL: URL) async throws -> ProviderStatus {\n        let apiURL = baseURL.appendingPathComponent(\"api/v2/status.json\")\n        var request = URLRequest(url: apiURL)\n        request.timeoutInterval = 10\n\n        let (data, _) = try await URLSession.shared.data(for: request, delegate: nil)\n\n        struct Response: Decodable {\n            struct Status: Decodable {\n                let indicator: String\n                let description: String?\n            }\n\n            struct Page: Decodable {\n                let updatedAt: Date?\n\n                private enum CodingKeys: String, CodingKey {\n                    case updatedAt = \"updated_at\"\n                }\n            }\n\n            let page: Page?\n            let status: Status\n        }\n\n        let decoder = JSONDecoder()\n        decoder.dateDecodingStrategy = .custom { decoder in\n            let container = try decoder.singleValueContainer()\n            let raw = try container.decode(String.self)\n            let formatter = ISO8601DateFormatter()\n            formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]\n            if let date = formatter.date(from: raw) { return date }\n            formatter.formatOptions = [.withInternetDateTime]\n            if let date = formatter.date(from: raw) { return date }\n            throw DecodingError.dataCorruptedError(in: container, debugDescription: \"Invalid ISO8601 date\")\n        }\n\n        let response = try decoder.decode(Response.self, from: data)\n        let indicator = ProviderStatusIndicator(rawValue: response.status.indicator) ?? .unknown\n        return ProviderStatus(\n            indicator: indicator,\n            description: response.status.description,\n            updatedAt: response.page?.updatedAt)\n    }\n\n    static func fetchWorkspaceStatus(productID: String) async throws -> ProviderStatus {\n        guard let url = URL(string: \"https://www.google.com/appsstatus/dashboard/incidents.json\") else {\n            throw URLError(.badURL)\n        }\n        var request = URLRequest(url: url)\n        request.timeoutInterval = 10\n        let (data, _) = try await URLSession.shared.data(for: request, delegate: nil)\n        return try Self.parseGoogleWorkspaceStatus(data: data, productID: productID)\n    }\n\n    static func parseGoogleWorkspaceStatus(data: Data, productID: String) throws -> ProviderStatus {\n        let decoder = JSONDecoder()\n        decoder.keyDecodingStrategy = .convertFromSnakeCase\n        decoder.dateDecodingStrategy = .custom { decoder in\n            let container = try decoder.singleValueContainer()\n            let raw = try container.decode(String.self)\n            let formatter = ISO8601DateFormatter()\n            formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]\n            if let date = formatter.date(from: raw) { return date }\n            formatter.formatOptions = [.withInternetDateTime]\n            if let date = formatter.date(from: raw) { return date }\n            throw DecodingError.dataCorruptedError(in: container, debugDescription: \"Invalid ISO8601 date\")\n        }\n\n        let incidents = try decoder.decode([GoogleWorkspaceIncident].self, from: data)\n        let active = incidents.filter { $0.isRelevant(productID: productID) && $0.isActive }\n        guard !active.isEmpty else {\n            return ProviderStatus(indicator: .none, description: nil, updatedAt: nil)\n        }\n\n        var best: (\n            indicator: ProviderStatusIndicator,\n            incident: GoogleWorkspaceIncident,\n            update: GoogleWorkspaceUpdate?)\n        best = (indicator: .none, incident: active[0], update: active[0].mostRecentUpdate ?? active[0].updates?.last)\n\n        for incident in active {\n            let update = incident.mostRecentUpdate ?? incident.updates?.last\n            let indicator = Self.workspaceIndicator(\n                status: update?.status ?? incident.statusImpact,\n                severity: incident.severity)\n            if Self.indicatorRank(indicator) <= Self.indicatorRank(best.indicator) { continue }\n            best = (indicator: indicator, incident: incident, update: update)\n        }\n\n        let description = Self.workspaceSummary(from: best.update?.text ?? best.incident.externalDesc)\n        let updatedAt = best.update?.when ?? best.incident.modified ?? best.incident.begin\n        return ProviderStatus(indicator: best.indicator, description: description, updatedAt: updatedAt)\n    }\n\n    private static func indicatorRank(_ indicator: ProviderStatusIndicator) -> Int {\n        switch indicator {\n        case .none: 0\n        case .maintenance: 1\n        case .minor: 2\n        case .major: 3\n        case .critical: 4\n        case .unknown: 1\n        }\n    }\n\n    private static func workspaceIndicator(status: String?, severity: String?) -> ProviderStatusIndicator {\n        switch status?.uppercased() {\n        case \"AVAILABLE\": return .none\n        case \"SERVICE_INFORMATION\": return .minor\n        case \"SERVICE_DISRUPTION\": return .major\n        case \"SERVICE_OUTAGE\": return .critical\n        case \"SERVICE_MAINTENANCE\", \"SCHEDULED_MAINTENANCE\": return .maintenance\n        default: break\n        }\n\n        switch severity?.lowercased() {\n        case \"low\": return .minor\n        case \"medium\": return .major\n        case \"high\": return .critical\n        default: return .minor\n        }\n    }\n\n    private static func workspaceSummary(from text: String?) -> String? {\n        guard let text else { return nil }\n        let normalized = text\n            .replacingOccurrences(of: \"\\r\\n\", with: \"\\n\")\n            .replacingOccurrences(of: \"\\r\", with: \"\\n\")\n        let lines = normalized.split(separator: \"\\n\", omittingEmptySubsequences: true)\n        for rawLine in lines {\n            let trimmed = rawLine.trimmingCharacters(in: .whitespacesAndNewlines)\n            if trimmed.isEmpty { continue }\n            let lower = trimmed.lowercased()\n            if lower.hasPrefix(\"**summary\") || lower.hasPrefix(\"**description\") || lower == \"summary\" {\n                continue\n            }\n            var cleaned = trimmed.replacingOccurrences(of: \"**\", with: \"\")\n            cleaned = cleaned.replacingOccurrences(\n                of: #\"\\[([^\\]]+)\\]\\([^)]+\\)\"#,\n                with: \"$1\",\n                options: .regularExpression)\n            if cleaned.hasPrefix(\"- \") {\n                cleaned.removeFirst(2)\n            }\n            cleaned = cleaned.trimmingCharacters(in: .whitespacesAndNewlines)\n            if !cleaned.isEmpty { return cleaned }\n        }\n        return nil\n    }\n\n    private struct GoogleWorkspaceIncident: Decodable {\n        let begin: Date?\n        let end: Date?\n        let modified: Date?\n        let externalDesc: String?\n        let statusImpact: String?\n        let severity: String?\n        let affectedProducts: [GoogleWorkspaceProduct]?\n        let currentlyAffectedProducts: [GoogleWorkspaceProduct]?\n        let mostRecentUpdate: GoogleWorkspaceUpdate?\n        let updates: [GoogleWorkspaceUpdate]?\n\n        var isActive: Bool {\n            self.end == nil\n        }\n\n        func isRelevant(productID: String) -> Bool {\n            if let current = currentlyAffectedProducts {\n                return current.contains(where: { $0.id == productID })\n            }\n            return self.affectedProducts?.contains(where: { $0.id == productID }) ?? false\n        }\n    }\n\n    private struct GoogleWorkspaceProduct: Decodable {\n        let title: String?\n        let id: String\n    }\n\n    private struct GoogleWorkspaceUpdate: Decodable {\n        let when: Date?\n        let status: String?\n        let text: String?\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/UsageStore+Timeout.swift",
    "content": "import Foundation\n\nextension UsageStore {\n    nonisolated static func runWithTimeout(\n        seconds: Double,\n        operation: @escaping @Sendable () async -> String) async -> String\n    {\n        await withTaskGroup(of: String?.self) { group -> String in\n            group.addTask { await operation() }\n            group.addTask {\n                try? await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000))\n                return nil\n            }\n            let result = await group.next()?.flatMap(\\.self)\n            group.cancelAll()\n            return result ?? \"Probe timed out after \\(Int(seconds))s\"\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/UsageStore+TokenAccounts.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nstruct TokenAccountUsageSnapshot: Identifiable {\n    let id: UUID\n    let account: ProviderTokenAccount\n    let snapshot: UsageSnapshot?\n    let error: String?\n    let sourceLabel: String?\n\n    init(account: ProviderTokenAccount, snapshot: UsageSnapshot?, error: String?, sourceLabel: String?) {\n        self.id = account.id\n        self.account = account\n        self.snapshot = snapshot\n        self.error = error\n        self.sourceLabel = sourceLabel\n    }\n}\n\nextension UsageStore {\n    func tokenAccounts(for provider: UsageProvider) -> [ProviderTokenAccount] {\n        guard TokenAccountSupportCatalog.support(for: provider) != nil else { return [] }\n        return self.settings.tokenAccounts(for: provider)\n    }\n\n    func shouldFetchAllTokenAccounts(provider: UsageProvider, accounts: [ProviderTokenAccount]) -> Bool {\n        guard TokenAccountSupportCatalog.support(for: provider) != nil else { return false }\n        return self.settings.showAllTokenAccountsInMenu && accounts.count > 1\n    }\n\n    func refreshTokenAccounts(provider: UsageProvider, accounts: [ProviderTokenAccount]) async {\n        let selectedAccount = self.settings.selectedTokenAccount(for: provider)\n        let limitedAccounts = self.limitedTokenAccounts(accounts, selected: selectedAccount)\n        let effectiveSelected = selectedAccount ?? limitedAccounts.first\n        var snapshots: [TokenAccountUsageSnapshot] = []\n        var selectedOutcome: ProviderFetchOutcome?\n        var selectedSnapshot: UsageSnapshot?\n\n        for account in limitedAccounts {\n            let override = TokenAccountOverride(provider: provider, account: account)\n            let outcome = await self.fetchOutcome(provider: provider, override: override)\n            let resolved = self.resolveAccountOutcome(outcome, provider: provider, account: account)\n            snapshots.append(resolved.snapshot)\n            if account.id == effectiveSelected?.id {\n                selectedOutcome = outcome\n                selectedSnapshot = resolved.usage\n            }\n        }\n\n        await MainActor.run {\n            self.accountSnapshots[provider] = snapshots\n        }\n\n        if let selectedOutcome {\n            await self.applySelectedOutcome(\n                selectedOutcome,\n                provider: provider,\n                account: effectiveSelected,\n                fallbackSnapshot: selectedSnapshot)\n        }\n    }\n\n    func limitedTokenAccounts(\n        _ accounts: [ProviderTokenAccount],\n        selected: ProviderTokenAccount?) -> [ProviderTokenAccount]\n    {\n        let limit = 6\n        if accounts.count <= limit { return accounts }\n        var limited = Array(accounts.prefix(limit))\n        if let selected, !limited.contains(where: { $0.id == selected.id }) {\n            limited.removeLast()\n            limited.append(selected)\n        }\n        return limited\n    }\n\n    func fetchOutcome(\n        provider: UsageProvider,\n        override: TokenAccountOverride?) async -> ProviderFetchOutcome\n    {\n        let descriptor = ProviderDescriptorRegistry.descriptor(for: provider)\n        let sourceMode = self.sourceMode(for: provider)\n        let snapshot = ProviderRegistry.makeSettingsSnapshot(settings: self.settings, tokenOverride: override)\n        let env = ProviderRegistry.makeEnvironment(\n            base: ProcessInfo.processInfo.environment,\n            provider: provider,\n            settings: self.settings,\n            tokenOverride: override)\n        let verbose = self.settings.isVerboseLoggingEnabled\n        let context = ProviderFetchContext(\n            runtime: .app,\n            sourceMode: sourceMode,\n            includeCredits: false,\n            webTimeout: 60,\n            webDebugDumpHTML: false,\n            verbose: verbose,\n            env: env,\n            settings: snapshot,\n            fetcher: self.codexFetcher,\n            claudeFetcher: self.claudeFetcher,\n            browserDetection: self.browserDetection)\n        return await descriptor.fetchOutcome(context: context)\n    }\n\n    func sourceMode(for provider: UsageProvider) -> ProviderSourceMode {\n        ProviderCatalog.implementation(for: provider)?\n            .sourceMode(context: ProviderSourceModeContext(provider: provider, settings: self.settings))\n            ?? .auto\n    }\n\n    private struct ResolvedAccountOutcome {\n        let snapshot: TokenAccountUsageSnapshot\n        let usage: UsageSnapshot?\n    }\n\n    private func resolveAccountOutcome(\n        _ outcome: ProviderFetchOutcome,\n        provider: UsageProvider,\n        account: ProviderTokenAccount) -> ResolvedAccountOutcome\n    {\n        switch outcome.result {\n        case let .success(result):\n            let scoped = result.usage.scoped(to: provider)\n            let labeled = self.applyAccountLabel(scoped, provider: provider, account: account)\n            let snapshot = TokenAccountUsageSnapshot(\n                account: account,\n                snapshot: labeled,\n                error: nil,\n                sourceLabel: result.sourceLabel)\n            return ResolvedAccountOutcome(snapshot: snapshot, usage: labeled)\n        case let .failure(error):\n            let snapshot = TokenAccountUsageSnapshot(\n                account: account,\n                snapshot: nil,\n                error: error.localizedDescription,\n                sourceLabel: nil)\n            return ResolvedAccountOutcome(snapshot: snapshot, usage: nil)\n        }\n    }\n\n    func applySelectedOutcome(\n        _ outcome: ProviderFetchOutcome,\n        provider: UsageProvider,\n        account: ProviderTokenAccount?,\n        fallbackSnapshot: UsageSnapshot?) async\n    {\n        await MainActor.run {\n            self.lastFetchAttempts[provider] = outcome.attempts\n        }\n        switch outcome.result {\n        case let .success(result):\n            let scoped = result.usage.scoped(to: provider)\n            let labeled: UsageSnapshot = if let account {\n                self.applyAccountLabel(scoped, provider: provider, account: account)\n            } else {\n                scoped\n            }\n            await MainActor.run {\n                self.handleSessionQuotaTransition(provider: provider, snapshot: labeled)\n                self.snapshots[provider] = labeled\n                self.lastSourceLabels[provider] = result.sourceLabel\n                self.errors[provider] = nil\n                self.failureGates[provider]?.recordSuccess()\n            }\n        case let .failure(error):\n            await MainActor.run {\n                let hadPriorData = self.snapshots[provider] != nil || fallbackSnapshot != nil\n                let shouldSurface = self.failureGates[provider]?\n                    .shouldSurfaceError(onFailureWithPriorData: hadPriorData) ?? true\n                if shouldSurface {\n                    self.errors[provider] = error.localizedDescription\n                    self.snapshots.removeValue(forKey: provider)\n                } else {\n                    self.errors[provider] = nil\n                }\n            }\n        }\n    }\n\n    func applyAccountLabel(\n        _ snapshot: UsageSnapshot,\n        provider: UsageProvider,\n        account: ProviderTokenAccount) -> UsageSnapshot\n    {\n        let label = account.label.trimmingCharacters(in: .whitespacesAndNewlines)\n        guard !label.isEmpty else { return snapshot }\n        let existing = snapshot.identity(for: provider)\n        let email = existing?.accountEmail?.trimmingCharacters(in: .whitespacesAndNewlines)\n        let resolvedEmail = (email?.isEmpty ?? true) ? label : email\n        let identity = ProviderIdentitySnapshot(\n            providerID: provider,\n            accountEmail: resolvedEmail,\n            accountOrganization: existing?.accountOrganization,\n            loginMethod: existing?.loginMethod)\n        return snapshot.withIdentity(identity)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/UsageStore+TokenCost.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nextension UsageStore {\n    func tokenSnapshot(for provider: UsageProvider) -> CostUsageTokenSnapshot? {\n        self.tokenSnapshots[provider]\n    }\n\n    func tokenError(for provider: UsageProvider) -> String? {\n        self.tokenErrors[provider]\n    }\n\n    func tokenLastAttemptAt(for provider: UsageProvider) -> Date? {\n        self.lastTokenFetchAt[provider]\n    }\n\n    func isTokenRefreshInFlight(for provider: UsageProvider) -> Bool {\n        self.tokenRefreshInFlight.contains(provider)\n    }\n\n    nonisolated static func costUsageCacheDirectory(\n        fileManager: FileManager = .default) -> URL\n    {\n        let root = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first!\n        return root\n            .appendingPathComponent(\"CodexBar\", isDirectory: true)\n            .appendingPathComponent(\"cost-usage\", isDirectory: true)\n    }\n\n    nonisolated static func tokenCostNoDataMessage(for provider: UsageProvider) -> String {\n        ProviderDescriptorRegistry.descriptor(for: provider).tokenCost.noDataMessage()\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/UsageStore+WidgetSnapshot.swift",
    "content": "import CodexBarCore\nimport Foundation\n#if canImport(WidgetKit)\nimport WidgetKit\n#endif\n\nextension UsageStore {\n    func persistWidgetSnapshot(reason: String) {\n        let snapshot = self.makeWidgetSnapshot()\n        Task.detached(priority: .utility) {\n            WidgetSnapshotStore.save(snapshot)\n            #if canImport(WidgetKit)\n            await MainActor.run {\n                WidgetCenter.shared.reloadAllTimelines()\n            }\n            #endif\n        }\n    }\n\n    private func makeWidgetSnapshot() -> WidgetSnapshot {\n        let enabledProviders = self.enabledProviders()\n        let entries = UsageProvider.allCases.compactMap { provider in\n            self.makeWidgetEntry(for: provider)\n        }\n        return WidgetSnapshot(entries: entries, enabledProviders: enabledProviders, generatedAt: Date())\n    }\n\n    private func makeWidgetEntry(for provider: UsageProvider) -> WidgetSnapshot.ProviderEntry? {\n        guard let snapshot = self.snapshots[provider] else { return nil }\n\n        let tokenSnapshot = self.tokenSnapshots[provider]\n        let dailyUsage = tokenSnapshot?.daily.map { entry in\n            WidgetSnapshot.DailyUsagePoint(\n                dayKey: entry.date,\n                totalTokens: entry.totalTokens,\n                costUSD: entry.costUSD)\n        } ?? []\n\n        let tokenUsage = Self.widgetTokenUsageSummary(from: tokenSnapshot)\n        let creditsRemaining = provider == .codex ? self.credits?.remaining : nil\n        let codeReviewRemaining = provider == .codex ? self.openAIDashboard?.codeReviewRemainingPercent : nil\n\n        return WidgetSnapshot.ProviderEntry(\n            provider: provider,\n            updatedAt: snapshot.updatedAt,\n            primary: snapshot.primary,\n            secondary: snapshot.secondary,\n            tertiary: snapshot.tertiary,\n            creditsRemaining: creditsRemaining,\n            codeReviewRemainingPercent: codeReviewRemaining,\n            tokenUsage: tokenUsage,\n            dailyUsage: dailyUsage)\n    }\n\n    private nonisolated static func widgetTokenUsageSummary(\n        from snapshot: CostUsageTokenSnapshot?) -> WidgetSnapshot.TokenUsageSummary?\n    {\n        guard let snapshot else { return nil }\n        let fallbackTokens = snapshot.daily.compactMap(\\.totalTokens).reduce(0, +)\n        let monthTokensValue = snapshot.last30DaysTokens ?? (fallbackTokens > 0 ? fallbackTokens : nil)\n        return WidgetSnapshot.TokenUsageSummary(\n            sessionCostUSD: snapshot.sessionCostUSD,\n            sessionTokens: snapshot.sessionTokens,\n            last30DaysCostUSD: snapshot.last30DaysCostUSD,\n            last30DaysTokens: monthTokensValue)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/UsageStore.swift",
    "content": "import AppKit\nimport CodexBarCore\nimport Foundation\nimport Observation\nimport SweetCookieKit\n\n// MARK: - Observation helpers\n\n@MainActor\nextension UsageStore {\n    var menuObservationToken: Int {\n        _ = self.snapshots\n        _ = self.errors\n        _ = self.lastSourceLabels\n        _ = self.lastFetchAttempts\n        _ = self.accountSnapshots\n        _ = self.tokenSnapshots\n        _ = self.tokenErrors\n        _ = self.tokenRefreshInFlight\n        _ = self.credits\n        _ = self.lastCreditsError\n        _ = self.openAIDashboard\n        _ = self.lastOpenAIDashboardError\n        _ = self.openAIDashboardRequiresLogin\n        _ = self.openAIDashboardCookieImportStatus\n        _ = self.openAIDashboardCookieImportDebugLog\n        _ = self.versions\n        _ = self.isRefreshing\n        _ = self.refreshingProviders\n        _ = self.pathDebugInfo\n        _ = self.statuses\n        _ = self.probeLogs\n        _ = self.historicalPaceRevision\n        return 0\n    }\n\n    func observeSettingsChanges() {\n        withObservationTracking {\n            _ = self.settings.refreshFrequency\n            _ = self.settings.statusChecksEnabled\n            _ = self.settings.sessionQuotaNotificationsEnabled\n            _ = self.settings.usageBarsShowUsed\n            _ = self.settings.costUsageEnabled\n            _ = self.settings.randomBlinkEnabled\n            _ = self.settings.configRevision\n            for implementation in ProviderCatalog.all {\n                implementation.observeSettings(self.settings)\n            }\n            _ = self.settings.showAllTokenAccountsInMenu\n            _ = self.settings.tokenAccountsByProvider\n            _ = self.settings.mergeIcons\n            _ = self.settings.selectedMenuProvider\n            _ = self.settings.debugLoadingPattern\n            _ = self.settings.debugKeepCLISessionsAlive\n            _ = self.settings.historicalTrackingEnabled\n        } onChange: { [weak self] in\n            Task { @MainActor [weak self] in\n                guard let self else { return }\n                self.observeSettingsChanges()\n                self.probeLogs = [:]\n                guard self.startupBehavior.automaticallyStartsBackgroundWork else { return }\n                self.startTimer()\n                self.updateProviderRuntimes()\n                await self.refreshHistoricalDatasetIfNeeded()\n                await self.refresh()\n            }\n        }\n    }\n}\n\n@MainActor\n@Observable\nfinal class UsageStore {\n    enum StartupBehavior {\n        case automatic\n        case full\n        case testing\n\n        var automaticallyStartsBackgroundWork: Bool {\n            switch self {\n            case .automatic, .full:\n                true\n            case .testing:\n                false\n            }\n        }\n\n        func resolved(isRunningTests: Bool) -> StartupBehavior {\n            switch self {\n            case .automatic:\n                isRunningTests ? .testing : .full\n            case .full, .testing:\n                self\n            }\n        }\n    }\n\n    var snapshots: [UsageProvider: UsageSnapshot] = [:]\n    var errors: [UsageProvider: String] = [:]\n    var lastSourceLabels: [UsageProvider: String] = [:]\n    var lastFetchAttempts: [UsageProvider: [ProviderFetchAttempt]] = [:]\n    var accountSnapshots: [UsageProvider: [TokenAccountUsageSnapshot]] = [:]\n    var tokenSnapshots: [UsageProvider: CostUsageTokenSnapshot] = [:]\n    var tokenErrors: [UsageProvider: String] = [:]\n    var tokenRefreshInFlight: Set<UsageProvider> = []\n    var credits: CreditsSnapshot?\n    var lastCreditsError: String?\n    var openAIDashboard: OpenAIDashboardSnapshot?\n    var lastOpenAIDashboardError: String?\n    var openAIDashboardRequiresLogin: Bool = false\n    var openAIDashboardCookieImportStatus: String?\n    var openAIDashboardCookieImportDebugLog: String?\n    var versions: [UsageProvider: String] = [:]\n    var isRefreshing = false\n    var refreshingProviders: Set<UsageProvider> = []\n    var debugForceAnimation = false\n    var pathDebugInfo: PathDebugSnapshot = .empty\n    var statuses: [UsageProvider: ProviderStatus] = [:]\n    var probeLogs: [UsageProvider: String] = [:]\n    var historicalPaceRevision: Int = 0\n    @ObservationIgnored private var lastCreditsSnapshot: CreditsSnapshot?\n    @ObservationIgnored private var creditsFailureStreak: Int = 0\n    @ObservationIgnored private var lastOpenAIDashboardSnapshot: OpenAIDashboardSnapshot?\n    @ObservationIgnored private var lastOpenAIDashboardTargetEmail: String?\n    @ObservationIgnored private var lastOpenAIDashboardCookieImportAttemptAt: Date?\n    @ObservationIgnored private var lastOpenAIDashboardCookieImportEmail: String?\n    @ObservationIgnored private var openAIWebAccountDidChange: Bool = false\n\n    @ObservationIgnored let codexFetcher: UsageFetcher\n    @ObservationIgnored let claudeFetcher: any ClaudeUsageFetching\n    @ObservationIgnored private let costUsageFetcher: CostUsageFetcher\n    @ObservationIgnored let browserDetection: BrowserDetection\n    @ObservationIgnored private let registry: ProviderRegistry\n    @ObservationIgnored let settings: SettingsStore\n    @ObservationIgnored private let sessionQuotaNotifier: any SessionQuotaNotifying\n    @ObservationIgnored private let sessionQuotaLogger = CodexBarLog.logger(LogCategories.sessionQuota)\n    @ObservationIgnored private let openAIWebLogger = CodexBarLog.logger(LogCategories.openAIWeb)\n    @ObservationIgnored private let tokenCostLogger = CodexBarLog.logger(LogCategories.tokenCost)\n    @ObservationIgnored let augmentLogger = CodexBarLog.logger(LogCategories.augment)\n    @ObservationIgnored let providerLogger = CodexBarLog.logger(LogCategories.providers)\n    @ObservationIgnored private var openAIWebDebugLines: [String] = []\n    @ObservationIgnored var failureGates: [UsageProvider: ConsecutiveFailureGate] = [:]\n    @ObservationIgnored var tokenFailureGates: [UsageProvider: ConsecutiveFailureGate] = [:]\n    @ObservationIgnored var providerSpecs: [UsageProvider: ProviderSpec] = [:]\n    @ObservationIgnored let providerMetadata: [UsageProvider: ProviderMetadata]\n    @ObservationIgnored var providerRuntimes: [UsageProvider: any ProviderRuntime] = [:]\n    @ObservationIgnored private var timerTask: Task<Void, Never>?\n    @ObservationIgnored private var tokenTimerTask: Task<Void, Never>?\n    @ObservationIgnored private var tokenRefreshSequenceTask: Task<Void, Never>?\n    @ObservationIgnored private var pathDebugRefreshTask: Task<Void, Never>?\n    @ObservationIgnored let historicalUsageHistoryStore: HistoricalUsageHistoryStore\n    @ObservationIgnored var codexHistoricalDataset: CodexHistoricalDataset?\n    @ObservationIgnored var codexHistoricalDatasetAccountKey: String?\n    @ObservationIgnored var lastKnownSessionRemaining: [UsageProvider: Double] = [:]\n    @ObservationIgnored var lastKnownSessionWindowSource: [UsageProvider: SessionQuotaWindowSource] = [:]\n    @ObservationIgnored var lastTokenFetchAt: [UsageProvider: Date] = [:]\n    @ObservationIgnored private var hasCompletedInitialRefresh: Bool = false\n    @ObservationIgnored private let tokenFetchTTL: TimeInterval = 60 * 60\n    @ObservationIgnored private let tokenFetchTimeout: TimeInterval = 10 * 60\n    @ObservationIgnored private let startupBehavior: StartupBehavior\n\n    init(\n        fetcher: UsageFetcher,\n        browserDetection: BrowserDetection,\n        claudeFetcher: (any ClaudeUsageFetching)? = nil,\n        costUsageFetcher: CostUsageFetcher = CostUsageFetcher(),\n        settings: SettingsStore,\n        registry: ProviderRegistry = .shared,\n        historicalUsageHistoryStore: HistoricalUsageHistoryStore = HistoricalUsageHistoryStore(),\n        sessionQuotaNotifier: any SessionQuotaNotifying = SessionQuotaNotifier(),\n        startupBehavior: StartupBehavior = .automatic)\n    {\n        self.codexFetcher = fetcher\n        self.browserDetection = browserDetection\n        self.claudeFetcher = claudeFetcher ?? ClaudeUsageFetcher(browserDetection: browserDetection)\n        self.costUsageFetcher = costUsageFetcher\n        self.settings = settings\n        self.registry = registry\n        self.historicalUsageHistoryStore = historicalUsageHistoryStore\n        self.sessionQuotaNotifier = sessionQuotaNotifier\n        self.startupBehavior = startupBehavior.resolved(isRunningTests: Self.isRunningTestsProcess())\n        self.providerMetadata = registry.metadata\n        self\n            .failureGates = Dictionary(\n                uniqueKeysWithValues: UsageProvider.allCases\n                    .map { ($0, ConsecutiveFailureGate()) })\n        self.tokenFailureGates = Dictionary(\n            uniqueKeysWithValues: UsageProvider.allCases\n                .map { ($0, ConsecutiveFailureGate()) })\n        self.providerSpecs = registry.specs(\n            settings: settings,\n            metadata: self.providerMetadata,\n            codexFetcher: fetcher,\n            claudeFetcher: self.claudeFetcher,\n            browserDetection: browserDetection)\n        self.providerRuntimes = Dictionary(uniqueKeysWithValues: ProviderCatalog.all.compactMap { implementation in\n            implementation.makeRuntime().map { (implementation.id, $0) }\n        })\n        self.logStartupState()\n        self.bindSettings()\n        self.pathDebugInfo = PathDebugSnapshot(\n            codexBinary: nil,\n            claudeBinary: nil,\n            geminiBinary: nil,\n            effectivePATH: PathBuilder.effectivePATH(purposes: [.rpc, .tty, .nodeTooling]),\n            loginShellPATH: LoginShellPathCache.shared.current?.joined(separator: \":\"))\n        guard self.startupBehavior.automaticallyStartsBackgroundWork else { return }\n        self.detectVersions()\n        self.updateProviderRuntimes()\n        Task { @MainActor [weak self] in\n            self?.schedulePathDebugInfoRefresh()\n        }\n        LoginShellPathCache.shared.captureOnce { [weak self] _ in\n            Task { @MainActor [weak self] in\n                self?.schedulePathDebugInfoRefresh()\n            }\n        }\n        Task { @MainActor [weak self] in\n            await self?.refreshHistoricalDatasetIfNeeded()\n        }\n        Task { await self.refresh() }\n        self.startTimer()\n        self.startTokenTimer()\n    }\n\n    private static func isRunningTestsProcess() -> Bool {\n        let environment = ProcessInfo.processInfo.environment\n        if environment[\"XCTestConfigurationFilePath\"] != nil { return true }\n        if environment[\"XCTestSessionIdentifier\"] != nil { return true }\n        if environment[\"SWIFT_TESTING_ENABLED\"] != nil { return true }\n        return CommandLine.arguments.contains { argument in\n            argument.contains(\"xctest\") || argument.contains(\"swift-testing\")\n        }\n    }\n\n    /// Returns the login method (plan type) for the specified provider, if available.\n    private func loginMethod(for provider: UsageProvider) -> String? {\n        self.snapshots[provider]?.loginMethod(for: provider)\n    }\n\n    /// Returns true if the Claude account appears to be a subscription (Max, Pro, Ultra, Team).\n    /// Returns false for API users or when plan cannot be determined.\n    func isClaudeSubscription() -> Bool {\n        Self.isSubscriptionPlan(self.loginMethod(for: .claude))\n    }\n\n    /// Determines if a login method string indicates a Claude subscription plan.\n    /// Known subscription indicators: Max, Pro, Ultra, Team (case-insensitive).\n    nonisolated static func isSubscriptionPlan(_ loginMethod: String?) -> Bool {\n        ClaudePlan.isSubscriptionLoginMethod(loginMethod)\n    }\n\n    func version(for provider: UsageProvider) -> String? {\n        self.versions[provider]\n    }\n\n    var preferredSnapshot: UsageSnapshot? {\n        for provider in self.enabledProviders() {\n            if let snap = self.snapshots[provider] { return snap }\n        }\n        return nil\n    }\n\n    var iconStyle: IconStyle {\n        let enabled = self.enabledProviders()\n        if enabled.count > 1 { return .combined }\n        if let provider = enabled.first {\n            return self.style(for: provider)\n        }\n        return .codex\n    }\n\n    var isStale: Bool {\n        for provider in self.enabledProviders() where self.errors[provider] != nil {\n            return true\n        }\n        return false\n    }\n\n    func enabledProviders() -> [UsageProvider] {\n        // Use cached enablement to avoid repeated UserDefaults lookups in animation ticks.\n        let enabled = self.settings.enabledProvidersOrdered(metadataByProvider: self.providerMetadata)\n        return enabled.filter { self.isProviderAvailable($0) }\n    }\n\n    /// Enabled providers without availability filtering. Used for display (switcher, merge-icons).\n    func enabledProvidersForDisplay() -> [UsageProvider] {\n        self.settings.enabledProvidersOrdered(metadataByProvider: self.providerMetadata)\n    }\n\n    var statusChecksEnabled: Bool {\n        self.settings.statusChecksEnabled\n    }\n\n    func metadata(for provider: UsageProvider) -> ProviderMetadata {\n        self.providerMetadata[provider]!\n    }\n\n    private var codexBrowserCookieOrder: BrowserCookieImportOrder {\n        self.metadata(for: .codex).browserCookieOrder ?? Browser.defaultImportOrder\n    }\n\n    func snapshot(for provider: UsageProvider) -> UsageSnapshot? {\n        self.snapshots[provider]\n    }\n\n    func sourceLabel(for provider: UsageProvider) -> String {\n        var label = self.lastSourceLabels[provider] ?? \"\"\n        if label.isEmpty {\n            let descriptor = ProviderDescriptorRegistry.descriptor(for: provider)\n            let modes = descriptor.fetchPlan.sourceModes\n            if modes.count == 1, let mode = modes.first {\n                label = mode.rawValue\n            } else {\n                let context = ProviderSourceLabelContext(\n                    provider: provider,\n                    settings: self.settings,\n                    store: self,\n                    descriptor: descriptor)\n                label = ProviderCatalog.implementation(for: provider)?\n                    .defaultSourceLabel(context: context)\n                    ?? \"auto\"\n            }\n        }\n\n        let context = ProviderSourceLabelContext(\n            provider: provider,\n            settings: self.settings,\n            store: self,\n            descriptor: ProviderDescriptorRegistry.descriptor(for: provider))\n        return ProviderCatalog.implementation(for: provider)?\n            .decorateSourceLabel(context: context, baseLabel: label)\n            ?? label\n    }\n\n    func fetchAttempts(for provider: UsageProvider) -> [ProviderFetchAttempt] {\n        self.lastFetchAttempts[provider] ?? []\n    }\n\n    func style(for provider: UsageProvider) -> IconStyle {\n        self.providerSpecs[provider]?.style ?? .codex\n    }\n\n    func isStale(provider: UsageProvider) -> Bool {\n        self.errors[provider] != nil\n    }\n\n    func isEnabled(_ provider: UsageProvider) -> Bool {\n        let enabled = self.settings.isProviderEnabledCached(\n            provider: provider,\n            metadataByProvider: self.providerMetadata)\n        guard enabled else { return false }\n        return self.isProviderAvailable(provider)\n    }\n\n    func isProviderAvailable(_ provider: UsageProvider) -> Bool {\n        // Availability should mirror the effective fetch environment, including token-account overrides.\n        // Otherwise providers (notably token-account-backed API providers) can fetch successfully but be\n        // hidden from the menu because their credentials are not in ProcessInfo's environment.\n        let environment = ProviderRegistry.makeEnvironment(\n            base: ProcessInfo.processInfo.environment,\n            provider: provider,\n            settings: self.settings,\n            tokenOverride: nil)\n        let context = ProviderAvailabilityContext(\n            provider: provider,\n            settings: self.settings,\n            environment: environment)\n        return ProviderCatalog.implementation(for: provider)?\n            .isAvailable(context: context)\n            ?? true\n    }\n\n    func performRuntimeAction(_ action: ProviderRuntimeAction, for provider: UsageProvider) async {\n        guard let runtime = self.providerRuntimes[provider] else { return }\n        let context = ProviderRuntimeContext(provider: provider, settings: self.settings, store: self)\n        await runtime.perform(action: action, context: context)\n    }\n\n    private func updateProviderRuntimes() {\n        for (provider, runtime) in self.providerRuntimes {\n            let context = ProviderRuntimeContext(provider: provider, settings: self.settings, store: self)\n            if self.isEnabled(provider) {\n                runtime.start(context: context)\n            } else {\n                runtime.stop(context: context)\n            }\n            runtime.settingsDidChange(context: context)\n        }\n    }\n\n    func refresh(forceTokenUsage: Bool = false) async {\n        guard !self.isRefreshing else { return }\n        let refreshPhase: ProviderRefreshPhase = self.hasCompletedInitialRefresh ? .regular : .startup\n\n        await ProviderRefreshContext.$current.withValue(refreshPhase) {\n            self.isRefreshing = true\n            defer {\n                self.isRefreshing = false\n                self.hasCompletedInitialRefresh = true\n            }\n\n            await withTaskGroup(of: Void.self) { group in\n                for provider in UsageProvider.allCases {\n                    group.addTask { await self.refreshProvider(provider) }\n                    group.addTask { await self.refreshStatus(provider) }\n                }\n                group.addTask { await self.refreshCreditsIfNeeded() }\n            }\n\n            // Token-cost usage can be slow; run it outside the refresh group so we don't block menu updates.\n            self.scheduleTokenRefresh(force: forceTokenUsage)\n\n            // OpenAI web scrape depends on the current Codex account email (which can change after login/account\n            // switch). Run this after Codex usage refresh so we don't accidentally scrape with stale credentials.\n            await self.refreshOpenAIDashboardIfNeeded(force: forceTokenUsage)\n\n            if self.openAIDashboardRequiresLogin {\n                await self.refreshProvider(.codex)\n                await self.refreshCreditsIfNeeded()\n            }\n\n            self.persistWidgetSnapshot(reason: \"refresh\")\n        }\n    }\n\n    /// For demo/testing: drop the snapshot so the loading animation plays, then restore the last snapshot.\n    func replayLoadingAnimation(duration: TimeInterval = 3) {\n        let current = self.preferredSnapshot\n        self.snapshots.removeAll()\n        self.debugForceAnimation = true\n        Task { @MainActor in\n            try? await Task.sleep(for: .seconds(duration))\n            if let current, let provider = self.enabledProviders().first {\n                self.snapshots[provider] = current\n            }\n            self.debugForceAnimation = false\n        }\n    }\n\n    // MARK: - Private\n\n    private func bindSettings() {\n        self.observeSettingsChanges()\n    }\n\n    private func startTimer() {\n        self.timerTask?.cancel()\n        guard let wait = self.settings.refreshFrequency.seconds else { return }\n\n        // Background poller so the menu stays responsive; canceled when settings change or store deallocates.\n        self.timerTask = Task.detached(priority: .utility) { [weak self] in\n            while !Task.isCancelled {\n                try? await Task.sleep(for: .seconds(wait))\n                await self?.refresh()\n            }\n        }\n    }\n\n    private func startTokenTimer() {\n        self.tokenTimerTask?.cancel()\n        let wait = self.tokenFetchTTL\n        self.tokenTimerTask = Task.detached(priority: .utility) { [weak self] in\n            while !Task.isCancelled {\n                try? await Task.sleep(for: .seconds(wait))\n                await self?.scheduleTokenRefresh(force: false)\n            }\n        }\n    }\n\n    private func scheduleTokenRefresh(force: Bool) {\n        if force {\n            self.tokenRefreshSequenceTask?.cancel()\n            self.tokenRefreshSequenceTask = nil\n        } else if self.tokenRefreshSequenceTask != nil {\n            return\n        }\n\n        self.tokenRefreshSequenceTask = Task(priority: .utility) { [weak self] in\n            guard let self else { return }\n            defer {\n                Task { @MainActor [weak self] in\n                    self?.tokenRefreshSequenceTask = nil\n                }\n            }\n            for provider in UsageProvider.allCases {\n                if Task.isCancelled { break }\n                await self.refreshTokenUsage(provider, force: force)\n            }\n        }\n    }\n\n    deinit {\n        self.timerTask?.cancel()\n        self.tokenTimerTask?.cancel()\n        self.tokenRefreshSequenceTask?.cancel()\n    }\n\n    enum SessionQuotaWindowSource: String {\n        case primary\n        case copilotSecondaryFallback\n    }\n\n    private func sessionQuotaWindow(\n        provider: UsageProvider,\n        snapshot: UsageSnapshot) -> (window: RateWindow, source: SessionQuotaWindowSource)?\n    {\n        if let primary = snapshot.primary {\n            return (primary, .primary)\n        }\n        if provider == .copilot, let secondary = snapshot.secondary {\n            return (secondary, .copilotSecondaryFallback)\n        }\n        return nil\n    }\n\n    func handleSessionQuotaTransition(provider: UsageProvider, snapshot: UsageSnapshot) {\n        // Session quota notifications are tied to the primary session window. Copilot free plans can\n        // expose only chat quota, so allow Copilot to fall back to secondary for transition tracking.\n        guard let sessionWindow = self.sessionQuotaWindow(provider: provider, snapshot: snapshot) else {\n            self.lastKnownSessionRemaining.removeValue(forKey: provider)\n            self.lastKnownSessionWindowSource.removeValue(forKey: provider)\n            return\n        }\n        let currentRemaining = sessionWindow.window.remainingPercent\n        let currentSource = sessionWindow.source\n        let previousRemaining = self.lastKnownSessionRemaining[provider]\n        let previousSource = self.lastKnownSessionWindowSource[provider]\n\n        if let previousSource, previousSource != currentSource {\n            let providerText = provider.rawValue\n            self.sessionQuotaLogger.debug(\n                \"session window source changed: provider=\\(providerText) prevSource=\\(previousSource.rawValue) \" +\n                    \"currSource=\\(currentSource.rawValue) curr=\\(currentRemaining)\")\n            self.lastKnownSessionRemaining[provider] = currentRemaining\n            self.lastKnownSessionWindowSource[provider] = currentSource\n            return\n        }\n\n        defer {\n            self.lastKnownSessionRemaining[provider] = currentRemaining\n            self.lastKnownSessionWindowSource[provider] = currentSource\n        }\n\n        guard self.settings.sessionQuotaNotificationsEnabled else {\n            if SessionQuotaNotificationLogic.isDepleted(currentRemaining) ||\n                SessionQuotaNotificationLogic.isDepleted(previousRemaining)\n            {\n                let providerText = provider.rawValue\n                let message =\n                    \"notifications disabled: provider=\\(providerText) \" +\n                    \"prev=\\(previousRemaining ?? -1) curr=\\(currentRemaining)\"\n                self.sessionQuotaLogger.debug(message)\n            }\n            return\n        }\n\n        guard previousRemaining != nil else {\n            if SessionQuotaNotificationLogic.isDepleted(currentRemaining) {\n                let providerText = provider.rawValue\n                let message = \"startup depleted: provider=\\(providerText) curr=\\(currentRemaining)\"\n                self.sessionQuotaLogger.info(message)\n                self.sessionQuotaNotifier.post(transition: .depleted, provider: provider, badge: nil)\n            }\n            return\n        }\n\n        let transition = SessionQuotaNotificationLogic.transition(\n            previousRemaining: previousRemaining,\n            currentRemaining: currentRemaining)\n        guard transition != .none else {\n            if SessionQuotaNotificationLogic.isDepleted(currentRemaining) ||\n                SessionQuotaNotificationLogic.isDepleted(previousRemaining)\n            {\n                let providerText = provider.rawValue\n                let message =\n                    \"no transition: provider=\\(providerText) \" +\n                    \"prev=\\(previousRemaining ?? -1) curr=\\(currentRemaining)\"\n                self.sessionQuotaLogger.debug(message)\n            }\n            return\n        }\n\n        let providerText = provider.rawValue\n        let transitionText = String(describing: transition)\n        let message =\n            \"transition \\(transitionText): provider=\\(providerText) \" +\n            \"prev=\\(previousRemaining ?? -1) curr=\\(currentRemaining)\"\n        self.sessionQuotaLogger.info(message)\n\n        self.sessionQuotaNotifier.post(transition: transition, provider: provider, badge: nil)\n    }\n\n    private func refreshStatus(_ provider: UsageProvider) async {\n        guard self.settings.statusChecksEnabled else { return }\n        guard let meta = self.providerMetadata[provider] else { return }\n\n        do {\n            let status: ProviderStatus\n            if let urlString = meta.statusPageURL, let baseURL = URL(string: urlString) {\n                status = try await Self.fetchStatus(from: baseURL)\n            } else if let productID = meta.statusWorkspaceProductID {\n                status = try await Self.fetchWorkspaceStatus(productID: productID)\n            } else {\n                return\n            }\n            await MainActor.run { self.statuses[provider] = status }\n        } catch {\n            // Keep the previous status to avoid flapping when the API hiccups.\n            await MainActor.run {\n                if self.statuses[provider] == nil {\n                    self.statuses[provider] = ProviderStatus(\n                        indicator: .unknown,\n                        description: error.localizedDescription,\n                        updatedAt: nil)\n                }\n            }\n        }\n    }\n\n    private func refreshCreditsIfNeeded() async {\n        guard self.isEnabled(.codex) else { return }\n        do {\n            let credits = try await self.codexFetcher.loadLatestCredits(\n                keepCLISessionsAlive: self.settings.debugKeepCLISessionsAlive)\n            await MainActor.run {\n                self.credits = credits\n                self.lastCreditsError = nil\n                self.lastCreditsSnapshot = credits\n                self.creditsFailureStreak = 0\n            }\n        } catch {\n            let message = error.localizedDescription\n            if message.localizedCaseInsensitiveContains(\"data not available yet\") {\n                await MainActor.run {\n                    if let cached = self.lastCreditsSnapshot {\n                        self.credits = cached\n                        self.lastCreditsError = nil\n                    } else {\n                        self.credits = nil\n                        self.lastCreditsError = \"Codex credits are still loading; will retry shortly.\"\n                    }\n                }\n                return\n            }\n\n            await MainActor.run {\n                self.creditsFailureStreak += 1\n                if let cached = self.lastCreditsSnapshot {\n                    self.credits = cached\n                    let stamp = cached.updatedAt.formatted(date: .abbreviated, time: .shortened)\n                    self.lastCreditsError =\n                        \"Last Codex credits refresh failed: \\(message). Cached values from \\(stamp).\"\n                } else {\n                    self.lastCreditsError = message\n                    self.credits = nil\n                }\n            }\n        }\n    }\n}\n\nextension UsageStore {\n    private static let openAIWebRefreshMultiplier: TimeInterval = 5\n    private static let openAIWebPrimaryFetchTimeout: TimeInterval = 15\n    private static let openAIWebRetryFetchTimeout: TimeInterval = 8\n\n    private func openAIWebRefreshIntervalSeconds() -> TimeInterval {\n        let base = max(self.settings.refreshFrequency.seconds ?? 0, 120)\n        return base * Self.openAIWebRefreshMultiplier\n    }\n\n    func requestOpenAIDashboardRefreshIfStale(reason: String) {\n        guard self.isEnabled(.codex), self.settings.codexCookieSource.isEnabled else { return }\n        let now = Date()\n        let refreshInterval = self.openAIWebRefreshIntervalSeconds()\n        let lastUpdatedAt = self.openAIDashboard?.updatedAt ?? self.lastOpenAIDashboardSnapshot?.updatedAt\n        if let lastUpdatedAt, now.timeIntervalSince(lastUpdatedAt) < refreshInterval { return }\n        let stamp = now.formatted(date: .abbreviated, time: .shortened)\n        self.logOpenAIWeb(\"[\\(stamp)] OpenAI web refresh request: \\(reason)\")\n        Task { await self.refreshOpenAIDashboardIfNeeded(force: true) }\n    }\n\n    private func applyOpenAIDashboard(_ dash: OpenAIDashboardSnapshot, targetEmail: String?) async {\n        await MainActor.run {\n            self.openAIDashboard = dash\n            self.lastOpenAIDashboardError = nil\n            self.lastOpenAIDashboardSnapshot = dash\n            self.openAIDashboardRequiresLogin = false\n            // Only fill gaps; OAuth/CLI remain the primary sources for usage + credits.\n            if self.snapshots[.codex] == nil,\n               let usage = dash.toUsageSnapshot(provider: .codex, accountEmail: targetEmail)\n            {\n                self.snapshots[.codex] = usage\n                self.errors[.codex] = nil\n                self.failureGates[.codex]?.recordSuccess()\n                self.lastSourceLabels[.codex] = \"openai-web\"\n            }\n            if self.credits == nil, let credits = dash.toCreditsSnapshot() {\n                self.credits = credits\n                self.lastCreditsSnapshot = credits\n                self.lastCreditsError = nil\n                self.creditsFailureStreak = 0\n            }\n        }\n\n        if let email = targetEmail, !email.isEmpty {\n            OpenAIDashboardCacheStore.save(OpenAIDashboardCache(accountEmail: email, snapshot: dash))\n        }\n        self.backfillCodexHistoricalFromDashboardIfNeeded(dash)\n    }\n\n    private func applyOpenAIDashboardFailure(message: String) async {\n        await MainActor.run {\n            if let cached = self.lastOpenAIDashboardSnapshot {\n                self.openAIDashboard = cached\n                let stamp = cached.updatedAt.formatted(date: .abbreviated, time: .shortened)\n                self.lastOpenAIDashboardError =\n                    \"Last OpenAI dashboard refresh failed: \\(message). Cached values from \\(stamp).\"\n            } else {\n                self.lastOpenAIDashboardError = message\n                self.openAIDashboard = nil\n            }\n        }\n    }\n\n    private func refreshOpenAIDashboardIfNeeded(force: Bool = false) async {\n        guard self.isEnabled(.codex), self.settings.codexCookieSource.isEnabled else {\n            self.resetOpenAIWebState()\n            return\n        }\n\n        let targetEmail = self.codexAccountEmailForOpenAIDashboard()\n        self.handleOpenAIWebTargetEmailChangeIfNeeded(targetEmail: targetEmail)\n\n        let now = Date()\n        let minInterval = self.openAIWebRefreshIntervalSeconds()\n        if !force,\n           !self.openAIWebAccountDidChange,\n           self.lastOpenAIDashboardError == nil,\n           let snapshot = self.lastOpenAIDashboardSnapshot,\n           now.timeIntervalSince(snapshot.updatedAt) < minInterval\n        {\n            return\n        }\n\n        if self.openAIWebDebugLines.isEmpty {\n            self.resetOpenAIWebDebugLog(context: \"refresh\")\n        } else {\n            let stamp = Date().formatted(date: .abbreviated, time: .shortened)\n            self.logOpenAIWeb(\"[\\(stamp)] OpenAI web refresh start\")\n        }\n        let log: (String) -> Void = { [weak self] line in\n            guard let self else { return }\n            self.logOpenAIWeb(line)\n        }\n\n        do {\n            let normalized = targetEmail?\n                .trimmingCharacters(in: .whitespacesAndNewlines)\n                .lowercased()\n            var effectiveEmail = targetEmail\n\n            // Use a per-email persistent `WKWebsiteDataStore` so multiple dashboard sessions can coexist.\n            // Strategy:\n            // - Try the existing per-email WebKit cookie store first (fast; avoids Keychain prompts).\n            // - On login-required or account mismatch, import cookies from the configured browser order and retry once.\n            if self.openAIWebAccountDidChange, let targetEmail, !targetEmail.isEmpty {\n                // On account switches, proactively re-import cookies so we don't show stale data from the previous\n                // user.\n                if let imported = await self.importOpenAIDashboardCookiesIfNeeded(\n                    targetEmail: targetEmail,\n                    force: true)\n                {\n                    effectiveEmail = imported\n                }\n                self.openAIWebAccountDidChange = false\n            }\n\n            var dash = try await OpenAIDashboardFetcher().loadLatestDashboard(\n                accountEmail: effectiveEmail,\n                logger: log,\n                debugDumpHTML: false,\n                timeout: Self.openAIWebPrimaryFetchTimeout)\n\n            if self.dashboardEmailMismatch(expected: normalized, actual: dash.signedInEmail) {\n                if let imported = await self.importOpenAIDashboardCookiesIfNeeded(\n                    targetEmail: targetEmail,\n                    force: true)\n                {\n                    effectiveEmail = imported\n                }\n                dash = try await OpenAIDashboardFetcher().loadLatestDashboard(\n                    accountEmail: effectiveEmail,\n                    logger: log,\n                    debugDumpHTML: false,\n                    timeout: Self.openAIWebRetryFetchTimeout)\n            }\n\n            if self.dashboardEmailMismatch(expected: normalized, actual: dash.signedInEmail) {\n                let signedIn = dash.signedInEmail?.trimmingCharacters(in: .whitespacesAndNewlines) ?? \"unknown\"\n                await MainActor.run {\n                    self.openAIDashboard = nil\n                    self.lastOpenAIDashboardError = [\n                        \"OpenAI dashboard signed in as \\(signedIn), but Codex uses \\(normalized ?? \"unknown\").\",\n                        \"Switch accounts in your browser and update OpenAI cookies in Providers → Codex.\",\n                    ].joined(separator: \" \")\n                    self.openAIDashboardRequiresLogin = true\n                }\n                return\n            }\n\n            await self.applyOpenAIDashboard(dash, targetEmail: effectiveEmail)\n        } catch let OpenAIDashboardFetcher.FetchError.noDashboardData(body) {\n            // Often indicates a missing/stale session without an obvious login prompt. Retry once after\n            // importing cookies from the user's browser.\n            let targetEmail = self.codexAccountEmailForOpenAIDashboard()\n            var effectiveEmail = targetEmail\n            if let imported = await self.importOpenAIDashboardCookiesIfNeeded(targetEmail: targetEmail, force: true) {\n                effectiveEmail = imported\n            }\n            do {\n                let dash = try await OpenAIDashboardFetcher().loadLatestDashboard(\n                    accountEmail: effectiveEmail,\n                    logger: log,\n                    debugDumpHTML: true,\n                    timeout: Self.openAIWebRetryFetchTimeout)\n                await self.applyOpenAIDashboard(dash, targetEmail: effectiveEmail)\n            } catch let OpenAIDashboardFetcher.FetchError.noDashboardData(retryBody) {\n                let finalBody = retryBody.isEmpty ? body : retryBody\n                let message = self.openAIDashboardFriendlyError(\n                    body: finalBody,\n                    targetEmail: targetEmail,\n                    cookieImportStatus: self.openAIDashboardCookieImportStatus)\n                    ?? OpenAIDashboardFetcher.FetchError.noDashboardData(body: finalBody).localizedDescription\n                await self.applyOpenAIDashboardFailure(message: message)\n            } catch {\n                await self.applyOpenAIDashboardFailure(message: error.localizedDescription)\n            }\n        } catch OpenAIDashboardFetcher.FetchError.loginRequired {\n            let targetEmail = self.codexAccountEmailForOpenAIDashboard()\n            var effectiveEmail = targetEmail\n            if let imported = await self.importOpenAIDashboardCookiesIfNeeded(targetEmail: targetEmail, force: true) {\n                effectiveEmail = imported\n            }\n            do {\n                let dash = try await OpenAIDashboardFetcher().loadLatestDashboard(\n                    accountEmail: effectiveEmail,\n                    logger: log,\n                    debugDumpHTML: true,\n                    timeout: Self.openAIWebRetryFetchTimeout)\n                await self.applyOpenAIDashboard(dash, targetEmail: effectiveEmail)\n            } catch OpenAIDashboardFetcher.FetchError.loginRequired {\n                await MainActor.run {\n                    self.lastOpenAIDashboardError = [\n                        \"OpenAI web access requires a signed-in chatgpt.com session.\",\n                        \"Sign in using \\(self.codexBrowserCookieOrder.loginHint), \" +\n                            \"then update OpenAI cookies in Providers → Codex.\",\n                    ].joined(separator: \" \")\n                    self.openAIDashboard = self.lastOpenAIDashboardSnapshot\n                    self.openAIDashboardRequiresLogin = true\n                }\n            } catch {\n                await self.applyOpenAIDashboardFailure(message: error.localizedDescription)\n            }\n        } catch {\n            await self.applyOpenAIDashboardFailure(message: error.localizedDescription)\n        }\n    }\n\n    // MARK: - OpenAI web account switching\n\n    /// Detect Codex account email changes and clear stale OpenAI web state so the UI can't show the wrong user.\n    /// This does not delete other per-email WebKit cookie stores (we keep multiple accounts around).\n    func handleOpenAIWebTargetEmailChangeIfNeeded(targetEmail: String?) {\n        let normalized = targetEmail?\n            .trimmingCharacters(in: .whitespacesAndNewlines)\n            .lowercased()\n\n        guard let normalized, !normalized.isEmpty else { return }\n\n        let previous = self.lastOpenAIDashboardTargetEmail\n        self.lastOpenAIDashboardTargetEmail = normalized\n\n        if let previous,\n           !previous.isEmpty,\n           previous != normalized\n        {\n            let stamp = Date().formatted(date: .abbreviated, time: .shortened)\n            self.logOpenAIWeb(\n                \"[\\(stamp)] Codex account changed: \\(previous) → \\(normalized); \" +\n                    \"clearing OpenAI web snapshot\")\n            self.openAIWebAccountDidChange = true\n            self.openAIDashboard = nil\n            self.lastOpenAIDashboardSnapshot = nil\n            self.lastOpenAIDashboardError = nil\n            self.openAIDashboardRequiresLogin = true\n            self.openAIDashboardCookieImportStatus = \"Codex account changed; importing browser cookies…\"\n            self.lastOpenAIDashboardCookieImportAttemptAt = nil\n            self.lastOpenAIDashboardCookieImportEmail = nil\n        }\n    }\n\n    func importOpenAIDashboardBrowserCookiesNow() async {\n        self.resetOpenAIWebDebugLog(context: \"manual import\")\n        let targetEmail = self.codexAccountEmailForOpenAIDashboard()\n        _ = await self.importOpenAIDashboardCookiesIfNeeded(targetEmail: targetEmail, force: true)\n        await self.refreshOpenAIDashboardIfNeeded(force: true)\n    }\n\n    private func importOpenAIDashboardCookiesIfNeeded(targetEmail: String?, force: Bool) async -> String? {\n        let normalizedTarget = targetEmail?.trimmingCharacters(in: .whitespacesAndNewlines)\n        let allowAnyAccount = normalizedTarget == nil || normalizedTarget?.isEmpty == true\n        let cookieSource = self.settings.codexCookieSource\n\n        let now = Date()\n        let lastEmail = self.lastOpenAIDashboardCookieImportEmail\n        let lastAttempt = self.lastOpenAIDashboardCookieImportAttemptAt ?? .distantPast\n\n        let shouldAttempt: Bool = if force {\n            true\n        } else {\n            if allowAnyAccount {\n                now.timeIntervalSince(lastAttempt) > 300\n            } else {\n                self.openAIDashboardRequiresLogin &&\n                    (\n                        lastEmail?.lowercased() != normalizedTarget?.lowercased() || now\n                            .timeIntervalSince(lastAttempt) > 300)\n            }\n        }\n\n        guard shouldAttempt else { return normalizedTarget }\n        self.lastOpenAIDashboardCookieImportEmail = normalizedTarget\n        self.lastOpenAIDashboardCookieImportAttemptAt = now\n\n        let stamp = now.formatted(date: .abbreviated, time: .shortened)\n        let targetLabel = normalizedTarget ?? \"unknown\"\n        self.logOpenAIWeb(\"[\\(stamp)] import start (target=\\(targetLabel))\")\n\n        do {\n            let log: (String) -> Void = { [weak self] message in\n                guard let self else { return }\n                self.logOpenAIWeb(message)\n            }\n\n            let importer = OpenAIDashboardBrowserCookieImporter(browserDetection: self.browserDetection)\n            let result: OpenAIDashboardBrowserCookieImporter.ImportResult\n            switch cookieSource {\n            case .manual:\n                self.settings.ensureCodexCookieLoaded()\n                let manualHeader = self.settings.codexCookieHeader\n                guard CookieHeaderNormalizer.normalize(manualHeader) != nil else {\n                    throw OpenAIDashboardBrowserCookieImporter.ImportError.manualCookieHeaderInvalid\n                }\n                result = try await importer.importManualCookies(\n                    cookieHeader: manualHeader,\n                    intoAccountEmail: normalizedTarget,\n                    allowAnyAccount: allowAnyAccount,\n                    logger: log)\n            case .auto:\n                result = try await importer.importBestCookies(\n                    intoAccountEmail: normalizedTarget,\n                    allowAnyAccount: allowAnyAccount,\n                    logger: log)\n            case .off:\n                result = OpenAIDashboardBrowserCookieImporter.ImportResult(\n                    sourceLabel: \"Off\",\n                    cookieCount: 0,\n                    signedInEmail: normalizedTarget,\n                    matchesCodexEmail: true)\n            }\n            let effectiveEmail = result.signedInEmail?\n                .trimmingCharacters(in: .whitespacesAndNewlines)\n                .isEmpty == false\n                ? result.signedInEmail?.trimmingCharacters(in: .whitespacesAndNewlines)\n                : normalizedTarget\n            self.lastOpenAIDashboardCookieImportEmail = effectiveEmail ?? normalizedTarget\n            await MainActor.run {\n                let signed = result.signedInEmail?.trimmingCharacters(in: .whitespacesAndNewlines)\n                let matchText = result.matchesCodexEmail ? \"matches Codex\" : \"does not match Codex\"\n                let sourceLabel = switch cookieSource {\n                case .manual:\n                    \"Manual cookie header\"\n                case .auto:\n                    \"\\(result.sourceLabel) cookies\"\n                case .off:\n                    \"OpenAI cookies disabled\"\n                }\n                if let signed, !signed.isEmpty {\n                    self.openAIDashboardCookieImportStatus =\n                        allowAnyAccount\n                            ? [\n                                \"Using \\(sourceLabel) (\\(result.cookieCount)).\",\n                                \"Signed in as \\(signed).\",\n                            ].joined(separator: \" \")\n                            : [\n                                \"Using \\(sourceLabel) (\\(result.cookieCount)).\",\n                                \"Signed in as \\(signed) (\\(matchText)).\",\n                            ].joined(separator: \" \")\n                } else {\n                    self.openAIDashboardCookieImportStatus =\n                        \"Using \\(sourceLabel) (\\(result.cookieCount)).\"\n                }\n            }\n            return effectiveEmail\n        } catch let err as OpenAIDashboardBrowserCookieImporter.ImportError {\n            switch err {\n            case let .noMatchingAccount(found):\n                let foundText: String = if found.isEmpty {\n                    \"no signed-in session detected in \\(self.codexBrowserCookieOrder.loginHint)\"\n                } else {\n                    found\n                        .sorted { lhs, rhs in\n                            if lhs.sourceLabel == rhs.sourceLabel { return lhs.email < rhs.email }\n                            return lhs.sourceLabel < rhs.sourceLabel\n                        }\n                        .map { \"\\($0.sourceLabel): \\($0.email)\" }\n                        .joined(separator: \" • \")\n                }\n                self.logOpenAIWeb(\"[\\(stamp)] import mismatch: \\(foundText)\")\n                await MainActor.run {\n                    self.openAIDashboardCookieImportStatus = allowAnyAccount\n                        ? [\n                            \"No signed-in OpenAI web session found.\",\n                            \"Found \\(foundText).\",\n                        ].joined(separator: \" \")\n                        : [\n                            \"Browser cookies do not match Codex account (\\(normalizedTarget ?? \"unknown\")).\",\n                            \"Found \\(foundText).\",\n                        ].joined(separator: \" \")\n                    // Treat mismatch like \"not logged in\" for the current Codex account.\n                    self.openAIDashboardRequiresLogin = true\n                    self.openAIDashboard = nil\n                }\n            case .noCookiesFound,\n                 .browserAccessDenied,\n                 .dashboardStillRequiresLogin,\n                 .manualCookieHeaderInvalid:\n                self.logOpenAIWeb(\"[\\(stamp)] import failed: \\(err.localizedDescription)\")\n                await MainActor.run {\n                    self.openAIDashboardCookieImportStatus =\n                        \"OpenAI cookie import failed: \\(err.localizedDescription)\"\n                    self.openAIDashboardRequiresLogin = true\n                }\n            }\n        } catch {\n            self.logOpenAIWeb(\"[\\(stamp)] import failed: \\(error.localizedDescription)\")\n            await MainActor.run {\n                self.openAIDashboardCookieImportStatus =\n                    \"Browser cookie import failed: \\(error.localizedDescription)\"\n            }\n        }\n        return nil\n    }\n\n    private func resetOpenAIWebDebugLog(context: String) {\n        let stamp = Date().formatted(date: .abbreviated, time: .shortened)\n        self.openAIWebDebugLines.removeAll(keepingCapacity: true)\n        self.openAIDashboardCookieImportDebugLog = nil\n        self.logOpenAIWeb(\"[\\(stamp)] OpenAI web \\(context) start\")\n    }\n\n    private func logOpenAIWeb(_ message: String) {\n        let safeMessage = LogRedactor.redact(message)\n        self.openAIWebLogger.debug(safeMessage)\n        self.openAIWebDebugLines.append(safeMessage)\n        if self.openAIWebDebugLines.count > 240 {\n            self.openAIWebDebugLines.removeFirst(self.openAIWebDebugLines.count - 240)\n        }\n        self.openAIDashboardCookieImportDebugLog = self.openAIWebDebugLines.joined(separator: \"\\n\")\n    }\n\n    func resetOpenAIWebState() {\n        self.openAIDashboard = nil\n        self.lastOpenAIDashboardError = nil\n        self.lastOpenAIDashboardSnapshot = nil\n        self.lastOpenAIDashboardTargetEmail = nil\n        self.openAIDashboardRequiresLogin = false\n        self.openAIDashboardCookieImportStatus = nil\n        self.openAIDashboardCookieImportDebugLog = nil\n        self.lastOpenAIDashboardCookieImportAttemptAt = nil\n        self.lastOpenAIDashboardCookieImportEmail = nil\n    }\n\n    private func dashboardEmailMismatch(expected: String?, actual: String?) -> Bool {\n        guard let expected, !expected.isEmpty else { return false }\n        guard let raw = actual?.trimmingCharacters(in: .whitespacesAndNewlines), !raw.isEmpty else { return false }\n        return raw.lowercased() != expected.lowercased()\n    }\n\n    func codexAccountEmailForOpenAIDashboard() -> String? {\n        let direct = self.snapshots[.codex]?.accountEmail(for: .codex)?\n            .trimmingCharacters(in: .whitespacesAndNewlines)\n        if let direct, !direct.isEmpty { return direct }\n        let fallback = self.codexFetcher.loadAccountInfo().email?.trimmingCharacters(in: .whitespacesAndNewlines)\n        if let fallback, !fallback.isEmpty { return fallback }\n        let cached = self.openAIDashboard?.signedInEmail?.trimmingCharacters(in: .whitespacesAndNewlines)\n        if let cached, !cached.isEmpty { return cached }\n        let imported = self.lastOpenAIDashboardCookieImportEmail?.trimmingCharacters(in: .whitespacesAndNewlines)\n        if let imported, !imported.isEmpty { return imported }\n        return nil\n    }\n}\n\nextension UsageStore {\n    func debugDumpClaude() async {\n        let fetcher = ClaudeUsageFetcher(\n            browserDetection: self.browserDetection,\n            keepCLISessionsAlive: self.settings.debugKeepCLISessionsAlive)\n        let output = await fetcher.debugRawProbe(model: \"sonnet\")\n        let url = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(\"codexbar-claude-probe.txt\")\n        try? output.write(to: url, atomically: true, encoding: .utf8)\n        await MainActor.run {\n            let snippet = String(output.prefix(180)).replacingOccurrences(of: \"\\n\", with: \" \")\n            self.errors[.claude] = \"[Claude] \\(snippet) (saved: \\(url.path))\"\n            NSWorkspace.shared.open(url)\n        }\n    }\n\n    func dumpLog(toFileFor provider: UsageProvider) async -> URL? {\n        let text = await self.debugLog(for: provider)\n        let filename = \"codexbar-\\(provider.rawValue)-probe.txt\"\n        let url = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(filename)\n        do {\n            try text.write(to: url, atomically: true, encoding: .utf8)\n            _ = await MainActor.run { NSWorkspace.shared.open(url) }\n            return url\n        } catch {\n            await MainActor.run {\n                self.errors[provider] = \"Failed to save log: \\(error.localizedDescription)\"\n            }\n            return nil\n        }\n    }\n\n    func debugAugmentDump() async -> String {\n        await AugmentStatusProbe.latestDumps()\n    }\n\n    func debugLog(for provider: UsageProvider) async -> String {\n        if let cached = self.probeLogs[provider], !cached.isEmpty {\n            return cached\n        }\n\n        let claudeWebExtrasEnabled = self.settings.claudeWebExtrasEnabled\n        let claudeUsageDataSource = self.settings.claudeUsageDataSource\n        let claudeCookieSource = self.settings.claudeCookieSource\n        let claudeCookieHeader = self.settings.claudeCookieHeader\n        let claudeDebugConfiguration: ClaudeDebugLogConfiguration? = if provider == .claude {\n            await self.makeClaudeDebugConfiguration(\n                fallbackUsageDataSource: claudeUsageDataSource,\n                fallbackWebExtrasEnabled: claudeWebExtrasEnabled,\n                fallbackCookieSource: claudeCookieSource,\n                fallbackCookieHeader: claudeCookieHeader)\n        } else {\n            nil\n        }\n        let cursorCookieSource = self.settings.cursorCookieSource\n        let cursorCookieHeader = self.settings.cursorCookieHeader\n        let ampCookieSource = self.settings.ampCookieSource\n        let ampCookieHeader = self.settings.ampCookieHeader\n        let ollamaCookieSource = self.settings.ollamaCookieSource\n        let ollamaCookieHeader = self.settings.ollamaCookieHeader\n        let processEnvironment = ProcessInfo.processInfo.environment\n        let openRouterConfigToken = self.settings.providerConfig(for: .openrouter)?.sanitizedAPIKey\n        let openRouterHasConfigToken = !(openRouterConfigToken?.trimmingCharacters(in: .whitespacesAndNewlines)\n            .isEmpty ?? true)\n        let openRouterHasEnvToken = OpenRouterSettingsReader.apiToken(environment: processEnvironment) != nil\n        let openRouterEnvironment = ProviderConfigEnvironment.applyAPIKeyOverride(\n            base: processEnvironment,\n            provider: .openrouter,\n            config: self.settings.providerConfig(for: .openrouter))\n        let codexFetcher = self.codexFetcher\n        let browserDetection = self.browserDetection\n        let claudeDebugExecutionContext = self.currentClaudeDebugExecutionContext()\n        let text = await Task.detached(priority: .utility) { () -> String in\n            let unimplementedDebugLogMessages: [UsageProvider: String] = [\n                .gemini: \"Gemini debug log not yet implemented\",\n                .antigravity: \"Antigravity debug log not yet implemented\",\n                .opencode: \"OpenCode debug log not yet implemented\",\n                .alibaba: \"Alibaba Coding Plan debug log not yet implemented\",\n                .factory: \"Droid debug log not yet implemented\",\n                .copilot: \"Copilot debug log not yet implemented\",\n                .vertexai: \"Vertex AI debug log not yet implemented\",\n                .kilo: \"Kilo debug log not yet implemented\",\n                .kiro: \"Kiro debug log not yet implemented\",\n                .kimi: \"Kimi debug log not yet implemented\",\n                .kimik2: \"Kimi K2 debug log not yet implemented\",\n                .jetbrains: \"JetBrains AI debug log not yet implemented\",\n            ]\n            let buildText = {\n                switch provider {\n                case .codex:\n                    return await codexFetcher.debugRawRateLimits()\n                case .claude:\n                    guard let claudeDebugConfiguration else {\n                        return \"Claude debug log configuration unavailable\"\n                    }\n                    return await claudeDebugExecutionContext.apply {\n                        await Self.debugClaudeLog(\n                            browserDetection: browserDetection,\n                            configuration: claudeDebugConfiguration)\n                    }\n                case .zai:\n                    let resolution = ProviderTokenResolver.zaiResolution()\n                    let hasAny = resolution != nil\n                    let source = resolution?.source.rawValue ?? \"none\"\n                    return \"Z_AI_API_KEY=\\(hasAny ? \"present\" : \"missing\") source=\\(source)\"\n                case .synthetic:\n                    let resolution = ProviderTokenResolver.syntheticResolution()\n                    let hasAny = resolution != nil\n                    let source = resolution?.source.rawValue ?? \"none\"\n                    return \"SYNTHETIC_API_KEY=\\(hasAny ? \"present\" : \"missing\") source=\\(source)\"\n                case .cursor:\n                    return await Self.debugCursorLog(\n                        browserDetection: browserDetection,\n                        cursorCookieSource: cursorCookieSource,\n                        cursorCookieHeader: cursorCookieHeader)\n                case .minimax:\n                    let tokenResolution = ProviderTokenResolver.minimaxTokenResolution()\n                    let cookieResolution = ProviderTokenResolver.minimaxCookieResolution()\n                    let tokenSource = tokenResolution?.source.rawValue ?? \"none\"\n                    let cookieSource = cookieResolution?.source.rawValue ?? \"none\"\n                    return \"MINIMAX_API_KEY=\\(tokenResolution == nil ? \"missing\" : \"present\") \" +\n                        \"source=\\(tokenSource) MINIMAX_COOKIE=\\(cookieResolution == nil ? \"missing\" : \"present\") \" +\n                        \"source=\\(cookieSource)\"\n                case .alibaba:\n                    let resolution = ProviderTokenResolver.alibabaTokenResolution()\n                    let hasAny = resolution != nil\n                    let source = resolution?.source.rawValue ?? \"none\"\n                    return \"ALIBABA_CODING_PLAN_API_KEY=\\(hasAny ? \"present\" : \"missing\") source=\\(source)\"\n                case .augment:\n                    return await Self.debugAugmentLog()\n                case .amp:\n                    return await Self.debugAmpLog(\n                        browserDetection: browserDetection,\n                        ampCookieSource: ampCookieSource,\n                        ampCookieHeader: ampCookieHeader)\n                case .ollama:\n                    return await Self.debugOllamaLog(\n                        browserDetection: browserDetection,\n                        ollamaCookieSource: ollamaCookieSource,\n                        ollamaCookieHeader: ollamaCookieHeader)\n                case .openrouter:\n                    let resolution = ProviderTokenResolver.openRouterResolution(environment: openRouterEnvironment)\n                    let hasAny = resolution != nil\n                    let source: String = if resolution == nil {\n                        \"none\"\n                    } else if openRouterHasConfigToken, openRouterHasEnvToken {\n                        \"settings-config (overrides env)\"\n                    } else if openRouterHasConfigToken {\n                        \"settings-config\"\n                    } else {\n                        resolution?.source.rawValue ?? \"environment\"\n                    }\n                    return \"OPENROUTER_API_KEY=\\(hasAny ? \"present\" : \"missing\") source=\\(source)\"\n                case .warp:\n                    let resolution = ProviderTokenResolver.warpResolution()\n                    let hasAny = resolution != nil\n                    let source = resolution?.source.rawValue ?? \"none\"\n                    return \"WARP_API_KEY=\\(hasAny ? \"present\" : \"missing\") source=\\(source)\"\n                case .gemini, .antigravity, .opencode, .factory, .copilot, .vertexai, .kilo, .kiro, .kimi,\n                     .kimik2, .jetbrains:\n                    return unimplementedDebugLogMessages[provider] ?? \"Debug log not yet implemented\"\n                }\n            }\n            return await claudeDebugExecutionContext.apply {\n                await buildText()\n            }\n        }.value\n        self.probeLogs[provider] = text\n        return text\n    }\n\n    private func makeClaudeDebugConfiguration(\n        fallbackUsageDataSource: ClaudeUsageDataSource,\n        fallbackWebExtrasEnabled: Bool,\n        fallbackCookieSource: ProviderCookieSource,\n        fallbackCookieHeader: String) async -> ClaudeDebugLogConfiguration\n    {\n        await MainActor.run {\n            let sourceMode = self.sourceMode(for: .claude)\n            let snapshot = ProviderRegistry.makeSettingsSnapshot(settings: self.settings, tokenOverride: nil)\n            let environment = ProviderRegistry.makeEnvironment(\n                base: ProcessInfo.processInfo.environment,\n                provider: .claude,\n                settings: self.settings,\n                tokenOverride: nil)\n            let claudeSettings = snapshot.claude ?? ProviderSettingsSnapshot.ClaudeProviderSettings(\n                usageDataSource: fallbackUsageDataSource,\n                webExtrasEnabled: fallbackWebExtrasEnabled,\n                cookieSource: fallbackCookieSource,\n                manualCookieHeader: fallbackCookieHeader)\n            return ClaudeDebugLogConfiguration(\n                runtime: CodexBarCore.ProviderRuntime.app,\n                sourceMode: sourceMode,\n                environment: environment,\n                webExtrasEnabled: claudeSettings.webExtrasEnabled,\n                usageDataSource: claudeSettings.usageDataSource,\n                cookieSource: claudeSettings.cookieSource,\n                cookieHeader: claudeSettings.manualCookieHeader ?? \"\",\n                keepCLISessionsAlive: snapshot.debugKeepCLISessionsAlive)\n        }\n    }\n\n    private struct ClaudeDebugExecutionContext {\n        let interaction: ProviderInteraction\n        let refreshPhase: ProviderRefreshPhase\n        #if DEBUG\n        let keychainServiceOverride: String?\n        let credentialsURLOverride: URL?\n        let testingOverrides: ClaudeOAuthCredentialsStore.TestingOverridesSnapshot\n        let keychainDeniedUntilStoreOverride: ClaudeOAuthKeychainAccessGate.DeniedUntilStore?\n        let keychainPromptModeOverride: ClaudeOAuthKeychainPromptMode?\n        let keychainReadStrategyOverride: ClaudeOAuthKeychainReadStrategy?\n        let cliPathOverride: String?\n        let statusFetchOverride: ClaudeStatusProbe.FetchOverride?\n        #endif\n\n        func apply<T>(_ operation: () async -> T) async -> T {\n            await ProviderInteractionContext.$current.withValue(self.interaction) {\n                await ProviderRefreshContext.$current.withValue(self.refreshPhase) {\n                    #if DEBUG\n                    return await KeychainCacheStore.withServiceOverrideForTesting(self.keychainServiceOverride) {\n                        await ClaudeOAuthCredentialsStore\n                            .withCredentialsURLOverrideForTesting(self.credentialsURLOverride) {\n                                await ClaudeOAuthCredentialsStore\n                                    .withTestingOverridesSnapshotForTask(self.testingOverrides) {\n                                        await ClaudeOAuthKeychainAccessGate\n                                            .withDeniedUntilStoreOverrideForTesting(self\n                                                .keychainDeniedUntilStoreOverride)\n                                            {\n                                                await ClaudeOAuthKeychainPromptPreference\n                                                    .withTaskOverrideForTesting(self.keychainPromptModeOverride) {\n                                                        await ClaudeOAuthKeychainReadStrategyPreference\n                                                            .withTaskOverrideForTesting(self\n                                                                .keychainReadStrategyOverride)\n                                                            {\n                                                                await ClaudeCLIResolver\n                                                                    .withResolvedBinaryPathOverrideForTesting(self\n                                                                        .cliPathOverride)\n                                                                    {\n                                                                        await ClaudeStatusProbe\n                                                                            .withFetchOverrideForTesting(self\n                                                                                .statusFetchOverride)\n                                                                            {\n                                                                                await operation()\n                                                                            }\n                                                                    }\n                                                            }\n                                                    }\n                                            }\n                                    }\n                            }\n                    }\n                    #else\n                    return await operation()\n                    #endif\n                }\n            }\n        }\n    }\n\n    private func currentClaudeDebugExecutionContext() -> ClaudeDebugExecutionContext {\n        #if DEBUG\n        ClaudeDebugExecutionContext(\n            interaction: ProviderInteractionContext.current,\n            refreshPhase: ProviderRefreshContext.current,\n            keychainServiceOverride: KeychainCacheStore.currentServiceOverrideForTesting,\n            credentialsURLOverride: ClaudeOAuthCredentialsStore.currentCredentialsURLOverrideForTesting,\n            testingOverrides: ClaudeOAuthCredentialsStore.currentTestingOverridesSnapshotForTask,\n            keychainDeniedUntilStoreOverride: ClaudeOAuthKeychainAccessGate.currentDeniedUntilStoreOverrideForTesting,\n            keychainPromptModeOverride: ClaudeOAuthKeychainPromptPreference.currentTaskOverrideForTesting,\n            keychainReadStrategyOverride: ClaudeOAuthKeychainReadStrategyPreference.currentTaskOverrideForTesting,\n            cliPathOverride: ClaudeCLIResolver.currentResolvedBinaryPathOverrideForTesting,\n            statusFetchOverride: ClaudeStatusProbe.currentFetchOverrideForTesting)\n        #else\n        ClaudeDebugExecutionContext(\n            interaction: ProviderInteractionContext.current,\n            refreshPhase: ProviderRefreshContext.current)\n        #endif\n    }\n\n    private static func debugCursorLog(\n        browserDetection: BrowserDetection,\n        cursorCookieSource: ProviderCookieSource,\n        cursorCookieHeader: String) async -> String\n    {\n        await runWithTimeout(seconds: 15) {\n            var lines: [String] = []\n\n            do {\n                let probe = CursorStatusProbe(browserDetection: browserDetection)\n                let snapshot: CursorStatusSnapshot = if cursorCookieSource == .manual,\n                                                        let normalizedHeader = CookieHeaderNormalizer\n                                                            .normalize(cursorCookieHeader)\n                {\n                    try await probe.fetchWithManualCookies(normalizedHeader)\n                } else {\n                    try await probe.fetch { msg in lines.append(\"[cursor-cookie] \\(msg)\") }\n                }\n\n                lines.append(\"\")\n                lines.append(\"Cursor Status Summary:\")\n                lines.append(\"membershipType=\\(snapshot.membershipType ?? \"nil\")\")\n                lines.append(\"accountEmail=\\(snapshot.accountEmail ?? \"nil\")\")\n                lines.append(\"planPercentUsed=\\(snapshot.planPercentUsed)%\")\n                lines.append(\"planUsedUSD=$\\(snapshot.planUsedUSD)\")\n                lines.append(\"planLimitUSD=$\\(snapshot.planLimitUSD)\")\n                lines.append(\"onDemandUsedUSD=$\\(snapshot.onDemandUsedUSD)\")\n                lines.append(\"onDemandLimitUSD=\\(snapshot.onDemandLimitUSD.map { \"$\\($0)\" } ?? \"nil\")\")\n                if let teamUsed = snapshot.teamOnDemandUsedUSD {\n                    lines.append(\"teamOnDemandUsedUSD=$\\(teamUsed)\")\n                }\n                if let teamLimit = snapshot.teamOnDemandLimitUSD {\n                    lines.append(\"teamOnDemandLimitUSD=$\\(teamLimit)\")\n                }\n                lines.append(\"billingCycleEnd=\\(snapshot.billingCycleEnd?.description ?? \"nil\")\")\n\n                if let rawJSON = snapshot.rawJSON {\n                    lines.append(\"\")\n                    lines.append(\"Raw API Response:\")\n                    lines.append(rawJSON)\n                }\n\n                return lines.joined(separator: \"\\n\")\n            } catch {\n                lines.append(\"\")\n                lines.append(\"Cursor probe failed: \\(error.localizedDescription)\")\n                return lines.joined(separator: \"\\n\")\n            }\n        }\n    }\n\n    private static func debugAugmentLog() async -> String {\n        await runWithTimeout(seconds: 15) {\n            let probe = AugmentStatusProbe()\n            return await probe.debugRawProbe()\n        }\n    }\n\n    private static func debugAmpLog(\n        browserDetection: BrowserDetection,\n        ampCookieSource: ProviderCookieSource,\n        ampCookieHeader: String) async -> String\n    {\n        await runWithTimeout(seconds: 15) {\n            let fetcher = AmpUsageFetcher(browserDetection: browserDetection)\n            let manualHeader = ampCookieSource == .manual\n                ? CookieHeaderNormalizer.normalize(ampCookieHeader)\n                : nil\n            return await fetcher.debugRawProbe(cookieHeaderOverride: manualHeader)\n        }\n    }\n\n    private static func debugOllamaLog(\n        browserDetection: BrowserDetection,\n        ollamaCookieSource: ProviderCookieSource,\n        ollamaCookieHeader: String) async -> String\n    {\n        await runWithTimeout(seconds: 15) {\n            let fetcher = OllamaUsageFetcher(browserDetection: browserDetection)\n            let manualHeader = ollamaCookieSource == .manual\n                ? CookieHeaderNormalizer.normalize(ollamaCookieHeader)\n                : nil\n            return await fetcher.debugRawProbe(\n                cookieHeaderOverride: manualHeader,\n                manualCookieMode: ollamaCookieSource == .manual)\n        }\n    }\n\n    private func detectVersions() {\n        let implementations = ProviderCatalog.all\n        let browserDetection = self.browserDetection\n        Task { @MainActor [weak self] in\n            let resolved = await Task.detached { () -> [UsageProvider: String] in\n                var resolved: [UsageProvider: String] = [:]\n                await withTaskGroup(of: (UsageProvider, String?).self) { group in\n                    for implementation in implementations {\n                        let context = ProviderVersionContext(\n                            provider: implementation.id,\n                            browserDetection: browserDetection)\n                        group.addTask {\n                            await (implementation.id, implementation.detectVersion(context: context))\n                        }\n                    }\n                    for await (provider, version) in group {\n                        guard let version, !version.isEmpty else { continue }\n                        resolved[provider] = version\n                    }\n                }\n                return resolved\n            }.value\n            self?.versions = resolved\n        }\n    }\n\n    @MainActor\n    private func schedulePathDebugInfoRefresh() {\n        self.pathDebugRefreshTask?.cancel()\n        self.pathDebugRefreshTask = Task { [weak self] in\n            do {\n                try await Task.sleep(nanoseconds: 150_000_000)\n            } catch {\n                return\n            }\n            await self?.refreshPathDebugInfo()\n        }\n    }\n\n    private func runBackgroundSnapshot(\n        _ snapshot: @escaping @Sendable () async -> PathDebugSnapshot) async\n    {\n        let result = await snapshot()\n        await MainActor.run {\n            self.pathDebugInfo = result\n        }\n    }\n\n    private func refreshPathDebugInfo() async {\n        await self.runBackgroundSnapshot {\n            await PathBuilder.debugSnapshotAsync(purposes: [.rpc, .tty, .nodeTooling])\n        }\n    }\n\n    func clearCostUsageCache() async -> String? {\n        let errorMessage: String? = await Task.detached(priority: .utility) {\n            let fm = FileManager.default\n            let cacheDirs = [\n                Self.costUsageCacheDirectory(fileManager: fm),\n            ]\n\n            for cacheDir in cacheDirs {\n                do {\n                    try fm.removeItem(at: cacheDir)\n                } catch let error as NSError {\n                    if error.domain == NSCocoaErrorDomain, error.code == NSFileNoSuchFileError { continue }\n                    return error.localizedDescription\n                }\n            }\n            return nil\n        }.value\n\n        guard errorMessage == nil else { return errorMessage }\n\n        self.tokenSnapshots.removeAll()\n        self.tokenErrors.removeAll()\n        self.lastTokenFetchAt.removeAll()\n        self.tokenFailureGates[.codex]?.reset()\n        self.tokenFailureGates[.claude]?.reset()\n        return nil\n    }\n\n    private func refreshTokenUsage(_ provider: UsageProvider, force: Bool) async {\n        guard provider == .codex || provider == .claude || provider == .vertexai else {\n            self.tokenSnapshots.removeValue(forKey: provider)\n            self.tokenErrors[provider] = nil\n            self.tokenFailureGates[provider]?.reset()\n            self.lastTokenFetchAt.removeValue(forKey: provider)\n            return\n        }\n\n        guard self.settings.costUsageEnabled else {\n            self.tokenSnapshots.removeValue(forKey: provider)\n            self.tokenErrors[provider] = nil\n            self.tokenFailureGates[provider]?.reset()\n            self.lastTokenFetchAt.removeValue(forKey: provider)\n            return\n        }\n\n        guard self.isEnabled(provider) else {\n            self.tokenSnapshots.removeValue(forKey: provider)\n            self.tokenErrors[provider] = nil\n            self.tokenFailureGates[provider]?.reset()\n            self.lastTokenFetchAt.removeValue(forKey: provider)\n            return\n        }\n\n        guard !self.tokenRefreshInFlight.contains(provider) else { return }\n\n        let now = Date()\n        if !force,\n           let last = self.lastTokenFetchAt[provider],\n           now.timeIntervalSince(last) < self.tokenFetchTTL\n        {\n            return\n        }\n        self.lastTokenFetchAt[provider] = now\n        self.tokenRefreshInFlight.insert(provider)\n        defer { self.tokenRefreshInFlight.remove(provider) }\n\n        let startedAt = Date()\n        let providerText = provider.rawValue\n        self.tokenCostLogger\n            .debug(\"cost usage start provider=\\(providerText) force=\\(force)\")\n\n        do {\n            let fetcher = self.costUsageFetcher\n            let timeoutSeconds = self.tokenFetchTimeout\n            let snapshot = try await withThrowingTaskGroup(of: CostUsageTokenSnapshot.self) { group in\n                group.addTask(priority: .utility) {\n                    try await fetcher.loadTokenSnapshot(\n                        provider: provider,\n                        now: now,\n                        forceRefresh: force,\n                        allowVertexClaudeFallback: !self.isEnabled(.claude))\n                }\n                group.addTask {\n                    try await Task.sleep(nanoseconds: UInt64(timeoutSeconds * 1_000_000_000))\n                    throw CostUsageError.timedOut(seconds: Int(timeoutSeconds))\n                }\n                defer { group.cancelAll() }\n                guard let snapshot = try await group.next() else { throw CancellationError() }\n                return snapshot\n            }\n\n            guard !snapshot.daily.isEmpty else {\n                self.tokenSnapshots.removeValue(forKey: provider)\n                self.tokenErrors[provider] = Self.tokenCostNoDataMessage(for: provider)\n                self.tokenFailureGates[provider]?.recordSuccess()\n                return\n            }\n            let duration = Date().timeIntervalSince(startedAt)\n            let sessionCost = snapshot.sessionCostUSD.map(UsageFormatter.usdString) ?? \"—\"\n            let monthCost = snapshot.last30DaysCostUSD.map(UsageFormatter.usdString) ?? \"—\"\n            let durationText = String(format: \"%.2f\", duration)\n            let message =\n                \"cost usage success provider=\\(providerText) \" +\n                \"duration=\\(durationText)s \" +\n                \"today=\\(sessionCost) \" +\n                \"30d=\\(monthCost)\"\n            self.tokenCostLogger.info(message)\n            self.tokenSnapshots[provider] = snapshot\n            self.tokenErrors[provider] = nil\n            self.tokenFailureGates[provider]?.recordSuccess()\n            self.persistWidgetSnapshot(reason: \"token-usage\")\n        } catch {\n            if error is CancellationError { return }\n            let duration = Date().timeIntervalSince(startedAt)\n            let msg = error.localizedDescription\n            let durationText = String(format: \"%.2f\", duration)\n            let message = \"cost usage failed provider=\\(providerText) duration=\\(durationText)s error=\\(msg)\"\n            self.tokenCostLogger.error(message)\n            let hadPriorData = self.tokenSnapshots[provider] != nil\n            let shouldSurface = self.tokenFailureGates[provider]?\n                .shouldSurfaceError(onFailureWithPriorData: hadPriorData) ?? true\n            if shouldSurface {\n                self.tokenErrors[provider] = error.localizedDescription\n                self.tokenSnapshots.removeValue(forKey: provider)\n            } else {\n                self.tokenErrors[provider] = nil\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBar/UsageStoreSupport.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nenum ProviderStatusIndicator: String {\n    case none\n    case minor\n    case major\n    case critical\n    case maintenance\n    case unknown\n\n    var hasIssue: Bool {\n        switch self {\n        case .none: false\n        default: true\n        }\n    }\n\n    var label: String {\n        switch self {\n        case .none: \"Operational\"\n        case .minor: \"Partial outage\"\n        case .major: \"Major outage\"\n        case .critical: \"Critical issue\"\n        case .maintenance: \"Maintenance\"\n        case .unknown: \"Status unknown\"\n        }\n    }\n}\n\nstruct ProviderStatus {\n    let indicator: ProviderStatusIndicator\n    let description: String?\n    let updatedAt: Date?\n}\n\n/// Tracks consecutive failures so we can ignore a single flake when we previously had fresh data.\nstruct ConsecutiveFailureGate {\n    private(set) var streak: Int = 0\n\n    mutating func recordSuccess() {\n        self.streak = 0\n    }\n\n    mutating func reset() {\n        self.streak = 0\n    }\n\n    /// Returns true when the caller should surface the error to the UI.\n    mutating func shouldSurfaceError(onFailureWithPriorData hadPriorData: Bool) -> Bool {\n        self.streak += 1\n        if hadPriorData, self.streak == 1 { return false }\n        return true\n    }\n}\n\n#if DEBUG\nextension UsageStore {\n    func _setSnapshotForTesting(_ snapshot: UsageSnapshot?, provider: UsageProvider) {\n        self.snapshots[provider] = snapshot?.scoped(to: provider)\n    }\n\n    func _setTokenSnapshotForTesting(_ snapshot: CostUsageTokenSnapshot?, provider: UsageProvider) {\n        self.tokenSnapshots[provider] = snapshot\n    }\n\n    func _setTokenErrorForTesting(_ error: String?, provider: UsageProvider) {\n        self.tokenErrors[provider] = error\n    }\n\n    func _setErrorForTesting(_ error: String?, provider: UsageProvider) {\n        self.errors[provider] = error\n    }\n\n    func _setCodexHistoricalDatasetForTesting(_ dataset: CodexHistoricalDataset?, accountKey: String? = nil) {\n        self.codexHistoricalDataset = dataset\n        self.codexHistoricalDatasetAccountKey = accountKey\n        self.historicalPaceRevision += 1\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/CodexBar/ZaiTokenStore.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Security\n\nprotocol ZaiTokenStoring: Sendable {\n    func loadToken() throws -> String?\n    func storeToken(_ token: String?) throws\n}\n\nenum ZaiTokenStoreError: LocalizedError {\n    case keychainStatus(OSStatus)\n    case invalidData\n\n    var errorDescription: String? {\n        switch self {\n        case let .keychainStatus(status):\n            \"Keychain error: \\(status)\"\n        case .invalidData:\n            \"Keychain returned invalid data.\"\n        }\n    }\n}\n\nstruct KeychainZaiTokenStore: ZaiTokenStoring {\n    private static let log = CodexBarLog.logger(LogCategories.zaiTokenStore)\n\n    private let service = \"com.steipete.CodexBar\"\n    private let account = \"zai-api-token\"\n\n    // Cache to reduce keychain access frequency\n    private nonisolated(unsafe) static var cachedToken: String?\n    private nonisolated(unsafe) static var cacheTimestamp: Date?\n    private static let cacheLock = NSLock()\n    private static let cacheTTL: TimeInterval = 1800 // 30 minutes\n\n    func loadToken() throws -> String? {\n        guard !KeychainAccessGate.isDisabled else {\n            Self.log.debug(\"Keychain access disabled; skipping token load\")\n            return nil\n        }\n        // Check cache first\n        Self.cacheLock.lock()\n        if let timestamp = Self.cacheTimestamp,\n           Date().timeIntervalSince(timestamp) < Self.cacheTTL\n        {\n            let cached = Self.cachedToken\n            Self.cacheLock.unlock()\n            Self.log.debug(\"Using cached Zai token\")\n            return cached\n        }\n        Self.cacheLock.unlock()\n        var result: CFTypeRef?\n        let query: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: self.service,\n            kSecAttrAccount as String: self.account,\n            kSecMatchLimit as String: kSecMatchLimitOne,\n            kSecReturnData as String: true,\n        ]\n\n        if case .interactionRequired = KeychainAccessPreflight\n            .checkGenericPassword(service: self.service, account: self.account)\n        {\n            KeychainPromptHandler.handler?(KeychainPromptContext(\n                kind: .zaiToken,\n                service: self.service,\n                account: self.account))\n        }\n\n        let status = SecItemCopyMatching(query as CFDictionary, &result)\n        if status == errSecItemNotFound {\n            // Cache the nil result\n            Self.cacheLock.lock()\n            Self.cachedToken = nil\n            Self.cacheTimestamp = Date()\n            Self.cacheLock.unlock()\n            return nil\n        }\n        guard status == errSecSuccess else {\n            Self.log.error(\"Keychain read failed: \\(status)\")\n            throw ZaiTokenStoreError.keychainStatus(status)\n        }\n\n        guard let data = result as? Data else {\n            throw ZaiTokenStoreError.invalidData\n        }\n        let token = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines)\n        let finalValue = (token?.isEmpty == false) ? token : nil\n\n        // Cache the result\n        Self.cacheLock.lock()\n        Self.cachedToken = finalValue\n        Self.cacheTimestamp = Date()\n        Self.cacheLock.unlock()\n\n        return finalValue\n    }\n\n    func storeToken(_ token: String?) throws {\n        guard !KeychainAccessGate.isDisabled else {\n            Self.log.debug(\"Keychain access disabled; skipping token store\")\n            return\n        }\n        let cleaned = token?.trimmingCharacters(in: .whitespacesAndNewlines)\n        if cleaned == nil || cleaned?.isEmpty == true {\n            try self.deleteTokenIfPresent()\n            // Invalidate cache\n            Self.cacheLock.lock()\n            Self.cachedToken = nil\n            Self.cacheTimestamp = nil\n            Self.cacheLock.unlock()\n            return\n        }\n\n        let data = cleaned!.data(using: .utf8)!\n        let query: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: self.service,\n            kSecAttrAccount as String: self.account,\n        ]\n        let attributes: [String: Any] = [\n            kSecValueData as String: data,\n            kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,\n        ]\n\n        let updateStatus = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)\n        if updateStatus == errSecSuccess {\n            // Update cache\n            Self.cacheLock.lock()\n            Self.cachedToken = cleaned\n            Self.cacheTimestamp = Date()\n            Self.cacheLock.unlock()\n            return\n        }\n        if updateStatus != errSecItemNotFound {\n            Self.log.error(\"Keychain update failed: \\(updateStatus)\")\n            throw ZaiTokenStoreError.keychainStatus(updateStatus)\n        }\n\n        var addQuery = query\n        for (key, value) in attributes {\n            addQuery[key] = value\n        }\n        let addStatus = SecItemAdd(addQuery as CFDictionary, nil)\n        guard addStatus == errSecSuccess else {\n            Self.log.error(\"Keychain add failed: \\(addStatus)\")\n            throw ZaiTokenStoreError.keychainStatus(addStatus)\n        }\n\n        // Update cache\n        Self.cacheLock.lock()\n        Self.cachedToken = cleaned\n        Self.cacheTimestamp = Date()\n        Self.cacheLock.unlock()\n    }\n\n    private func deleteTokenIfPresent() throws {\n        guard !KeychainAccessGate.isDisabled else { return }\n        let query: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: self.service,\n            kSecAttrAccount as String: self.account,\n        ]\n        let status = SecItemDelete(query as CFDictionary)\n        if status == errSecSuccess || status == errSecItemNotFound {\n            // Invalidate cache\n            Self.cacheLock.lock()\n            Self.cachedToken = nil\n            Self.cacheTimestamp = nil\n            Self.cacheLock.unlock()\n            return\n        }\n        Self.log.error(\"Keychain delete failed: \\(status)\")\n        throw ZaiTokenStoreError.keychainStatus(status)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCLI/CLIConfigCommand.swift",
    "content": "import CodexBarCore\nimport Commander\nimport Foundation\n\nextension CodexBarCLI {\n    static func runConfigValidate(_ values: ParsedValues) {\n        let output = CLIOutputPreferences.from(values: values)\n        let config = Self.loadConfig(output: output)\n        let issues = CodexBarConfigValidator.validate(config)\n        let hasErrors = issues.contains(where: { $0.severity == .error })\n\n        switch output.format {\n        case .text:\n            if issues.isEmpty {\n                print(\"Config: OK\")\n            } else {\n                for issue in issues {\n                    let provider = issue.provider?.rawValue ?? \"config\"\n                    let field = issue.field ?? \"\"\n                    let prefix = \"[\\(issue.severity.rawValue.uppercased())]\"\n                    let suffix = field.isEmpty ? \"\" : \" (\\(field))\"\n                    print(\"\\(prefix) \\(provider)\\(suffix): \\(issue.message)\")\n                }\n            }\n        case .json:\n            Self.printJSON(issues, pretty: output.pretty)\n        }\n\n        Self.exit(code: hasErrors ? .failure : .success, output: output, kind: .config)\n    }\n\n    static func runConfigDump(_ values: ParsedValues) {\n        let output = CLIOutputPreferences.from(values: values)\n        let config = Self.loadConfig(output: output)\n        Self.printJSON(config, pretty: output.pretty)\n        Self.exit(code: .success, output: output, kind: .config)\n    }\n}\n\nstruct ConfigOptions: CommanderParsable {\n    @Flag(names: [.short(\"v\"), .long(\"verbose\")], help: \"Enable verbose logging\")\n    var verbose: Bool = false\n\n    @Flag(name: .long(\"json-output\"), help: \"Emit machine-readable logs\")\n    var jsonOutput: Bool = false\n\n    @Option(name: .long(\"log-level\"), help: \"Set log level (trace|verbose|debug|info|warning|error|critical)\")\n    var logLevel: String?\n\n    @Option(name: .long(\"format\"), help: \"Output format: text | json\")\n    var format: OutputFormat?\n\n    @Flag(name: .long(\"json\"), help: \"\")\n    var jsonShortcut: Bool = false\n\n    @Flag(name: .long(\"json-only\"), help: \"Emit JSON only (suppress non-JSON output)\")\n    var jsonOnly: Bool = false\n\n    @Flag(name: .long(\"pretty\"), help: \"Pretty-print JSON output\")\n    var pretty: Bool = false\n}\n"
  },
  {
    "path": "Sources/CodexBarCLI/CLICostCommand.swift",
    "content": "import CodexBarCore\nimport Commander\nimport Foundation\n\nextension CodexBarCLI {\n    private static let costSupportedProviders: Set<UsageProvider> = [.claude, .codex]\n\n    static func runCost(_ values: ParsedValues) async {\n        let output = CLIOutputPreferences.from(values: values)\n        let config = CodexBarCLI.loadConfig(output: output)\n        let selection = CodexBarCLI.decodeProvider(from: values, config: config)\n        let providers = Self.costProviders(from: selection)\n        let unsupported = selection.asList.filter { !Self.costSupportedProviders.contains($0) }\n        if !unsupported.isEmpty {\n            let names = unsupported\n                .map { ProviderDescriptorRegistry.descriptor(for: $0).metadata.displayName }\n                .sorted()\n                .joined(separator: \", \")\n            if !output.jsonOnly {\n                Self.writeStderr(\"Skipping providers without local cost usage: \\(names)\\n\")\n            }\n        }\n        guard !providers.isEmpty else {\n            Self.exit(\n                code: .failure,\n                message: \"Error: cost is only supported for Claude and Codex.\",\n                output: output,\n                kind: .args)\n        }\n\n        let format = output.format\n        let forceRefresh = values.flags.contains(\"refresh\")\n        let useColor = Self.shouldUseColor(noColor: values.flags.contains(\"noColor\"), format: format)\n\n        let fetcher = CostUsageFetcher()\n        var sections: [String] = []\n        var payload: [CostPayload] = []\n        var exitCode: ExitCode = .success\n\n        for provider in providers {\n            do {\n                // Cost usage is local-only; it does not require web/CLI provider fetches.\n                let snapshot = try await fetcher.loadTokenSnapshot(\n                    provider: provider,\n                    forceRefresh: forceRefresh)\n                switch format {\n                case .text:\n                    sections.append(Self.renderCostText(provider: provider, snapshot: snapshot, useColor: useColor))\n                case .json:\n                    payload.append(Self.makeCostPayload(provider: provider, snapshot: snapshot, error: nil))\n                }\n            } catch {\n                exitCode = Self.mapError(error)\n                if format == .json {\n                    payload.append(Self.makeCostPayload(provider: provider, snapshot: nil, error: error))\n                } else if !output.jsonOnly {\n                    Self.writeStderr(\"Error: \\(error.localizedDescription)\\n\")\n                }\n            }\n        }\n\n        switch format {\n        case .text:\n            if !sections.isEmpty {\n                print(sections.joined(separator: \"\\n\\n\"))\n            }\n        case .json:\n            if !payload.isEmpty {\n                Self.printJSON(payload, pretty: output.pretty)\n            }\n        }\n\n        Self.exit(code: exitCode, output: output, kind: exitCode == .success ? .runtime : .provider)\n    }\n\n    static func renderCostText(\n        provider: UsageProvider,\n        snapshot: CostUsageTokenSnapshot,\n        useColor: Bool) -> String\n    {\n        let name = ProviderDescriptorRegistry.descriptor(for: provider).metadata.displayName\n        let header = Self.costHeaderLine(\"\\(name) Cost (local)\", useColor: useColor)\n\n        let todayCost = snapshot.sessionCostUSD.map { UsageFormatter.usdString($0) } ?? \"—\"\n        let todayTokens = snapshot.sessionTokens.map { UsageFormatter.tokenCountString($0) }\n        let todayLine = todayTokens.map { \"Today: \\(todayCost) · \\($0) tokens\" } ?? \"Today: \\(todayCost)\"\n\n        let monthCost = snapshot.last30DaysCostUSD.map { UsageFormatter.usdString($0) } ?? \"—\"\n        let monthTokens = snapshot.last30DaysTokens.map { UsageFormatter.tokenCountString($0) }\n        let monthLine = monthTokens.map { \"Last 30 days: \\(monthCost) · \\($0) tokens\" } ?? \"Last 30 days: \\(monthCost)\"\n\n        return [header, todayLine, monthLine].joined(separator: \"\\n\")\n    }\n\n    private static func costHeaderLine(_ header: String, useColor: Bool) -> String {\n        guard useColor else { return header }\n        return \"\\u{001B}[1;36m\\(header)\\u{001B}[0m\"\n    }\n\n    private static func costProviders(from selection: ProviderSelection) -> [UsageProvider] {\n        selection.asList.filter { Self.costSupportedProviders.contains($0) }\n    }\n\n    private static func makeCostPayload(\n        provider: UsageProvider,\n        snapshot: CostUsageTokenSnapshot?,\n        error: Error?) -> CostPayload\n    {\n        let daily = snapshot?.daily.map { entry in\n            CostDailyEntryPayload(\n                date: entry.date,\n                inputTokens: entry.inputTokens,\n                outputTokens: entry.outputTokens,\n                cacheReadTokens: entry.cacheReadTokens,\n                cacheCreationTokens: entry.cacheCreationTokens,\n                totalTokens: entry.totalTokens,\n                costUSD: entry.costUSD,\n                modelsUsed: entry.modelsUsed,\n                modelBreakdowns: entry.modelBreakdowns?.map { breakdown in\n                    CostModelBreakdownPayload(\n                        modelName: breakdown.modelName,\n                        costUSD: breakdown.costUSD,\n                        totalTokens: breakdown.totalTokens)\n                })\n        } ?? []\n\n        return CostPayload(\n            provider: provider.rawValue,\n            source: \"local\",\n            updatedAt: snapshot?.updatedAt ?? (error == nil ? nil : Date()),\n            sessionTokens: snapshot?.sessionTokens,\n            sessionCostUSD: snapshot?.sessionCostUSD,\n            last30DaysTokens: snapshot?.last30DaysTokens,\n            last30DaysCostUSD: snapshot?.last30DaysCostUSD,\n            daily: daily,\n            totals: snapshot.flatMap(Self.costTotals(from:)),\n            error: error.map { Self.makeErrorPayload($0) })\n    }\n\n    private static func costTotals(from snapshot: CostUsageTokenSnapshot) -> CostTotalsPayload? {\n        let entries = snapshot.daily\n        guard !entries.isEmpty else {\n            guard snapshot.last30DaysTokens != nil || snapshot.last30DaysCostUSD != nil else { return nil }\n            return CostTotalsPayload(\n                totalInputTokens: nil,\n                totalOutputTokens: nil,\n                cacheReadTokens: nil,\n                cacheCreationTokens: nil,\n                totalTokens: snapshot.last30DaysTokens,\n                totalCostUSD: snapshot.last30DaysCostUSD)\n        }\n\n        var totalInput = 0\n        var totalOutput = 0\n        var totalCacheRead = 0\n        var totalCacheCreation = 0\n        var totalTokens = 0\n        var totalCost = 0.0\n        var sawInput = false\n        var sawOutput = false\n        var sawCacheRead = false\n        var sawCacheCreation = false\n        var sawTokens = false\n        var sawCost = false\n\n        for entry in entries {\n            if let input = entry.inputTokens {\n                totalInput += input\n                sawInput = true\n            }\n            if let output = entry.outputTokens {\n                totalOutput += output\n                sawOutput = true\n            }\n            if let cacheRead = entry.cacheReadTokens {\n                totalCacheRead += cacheRead\n                sawCacheRead = true\n            }\n            if let cacheCreation = entry.cacheCreationTokens {\n                totalCacheCreation += cacheCreation\n                sawCacheCreation = true\n            }\n            if let tokens = entry.totalTokens {\n                totalTokens += tokens\n                sawTokens = true\n            }\n            if let cost = entry.costUSD {\n                totalCost += cost\n                sawCost = true\n            }\n        }\n\n        // Prefer totals derived from daily rows; fall back to snapshot aggregates when rows omit fields.\n        return CostTotalsPayload(\n            totalInputTokens: sawInput ? totalInput : nil,\n            totalOutputTokens: sawOutput ? totalOutput : nil,\n            cacheReadTokens: sawCacheRead ? totalCacheRead : nil,\n            cacheCreationTokens: sawCacheCreation ? totalCacheCreation : nil,\n            totalTokens: sawTokens ? totalTokens : snapshot.last30DaysTokens,\n            totalCostUSD: sawCost ? totalCost : snapshot.last30DaysCostUSD)\n    }\n}\n\nstruct CostOptions: CommanderParsable {\n    @Flag(names: [.short(\"v\"), .long(\"verbose\")], help: \"Enable verbose logging\")\n    var verbose: Bool = false\n\n    @Flag(name: .long(\"json-output\"), help: \"Emit machine-readable logs\")\n    var jsonOutput: Bool = false\n\n    @Option(name: .long(\"log-level\"), help: \"Set log level (trace|verbose|debug|info|warning|error|critical)\")\n    var logLevel: String?\n\n    @Option(\n        name: .long(\"provider\"),\n        help: ProviderHelp.optionHelp)\n    var provider: ProviderSelection?\n\n    @Option(name: .long(\"format\"), help: \"Output format: text | json\")\n    var format: OutputFormat?\n\n    @Flag(name: .long(\"json\"), help: \"\")\n    var jsonShortcut: Bool = false\n\n    @Flag(name: .long(\"json-only\"), help: \"Emit JSON only (suppress non-JSON output)\")\n    var jsonOnly: Bool = false\n\n    @Flag(name: .long(\"pretty\"), help: \"Pretty-print JSON output\")\n    var pretty: Bool = false\n\n    @Flag(name: .long(\"no-color\"), help: \"Disable ANSI colors in text output\")\n    var noColor: Bool = false\n\n    @Flag(name: .long(\"refresh\"), help: \"Force refresh by ignoring cached scans\")\n    var refresh: Bool = false\n}\n\nstruct CostPayload: Encodable {\n    let provider: String\n    let source: String\n    let updatedAt: Date?\n    let sessionTokens: Int?\n    let sessionCostUSD: Double?\n    let last30DaysTokens: Int?\n    let last30DaysCostUSD: Double?\n    let daily: [CostDailyEntryPayload]\n    let totals: CostTotalsPayload?\n    let error: ProviderErrorPayload?\n}\n\nstruct CostDailyEntryPayload: Encodable {\n    let date: String\n    let inputTokens: Int?\n    let outputTokens: Int?\n    let cacheReadTokens: Int?\n    let cacheCreationTokens: Int?\n    let totalTokens: Int?\n    let costUSD: Double?\n    let modelsUsed: [String]?\n    let modelBreakdowns: [CostModelBreakdownPayload]?\n\n    private enum CodingKeys: String, CodingKey {\n        case date\n        case inputTokens\n        case outputTokens\n        case cacheReadTokens\n        case cacheCreationTokens\n        case totalTokens\n        case costUSD = \"totalCost\"\n        case modelsUsed\n        case modelBreakdowns\n    }\n}\n\nstruct CostModelBreakdownPayload: Encodable {\n    let modelName: String\n    let costUSD: Double?\n    let totalTokens: Int?\n\n    private enum CodingKeys: String, CodingKey {\n        case modelName\n        case costUSD = \"cost\"\n        case totalTokens\n    }\n}\n\nstruct CostTotalsPayload: Encodable {\n    let totalInputTokens: Int?\n    let totalOutputTokens: Int?\n    let cacheReadTokens: Int?\n    let cacheCreationTokens: Int?\n    let totalTokens: Int?\n    let totalCostUSD: Double?\n\n    private enum CodingKeys: String, CodingKey {\n        case totalInputTokens = \"inputTokens\"\n        case totalOutputTokens = \"outputTokens\"\n        case cacheReadTokens\n        case cacheCreationTokens\n        case totalTokens\n        case totalCostUSD = \"totalCost\"\n    }\n}\n\n// Intentionally empty.\n"
  },
  {
    "path": "Sources/CodexBarCLI/CLIEntry.swift",
    "content": "import CodexBarCore\nimport Commander\n#if canImport(AppKit)\nimport AppKit\n#endif\n#if canImport(Darwin)\nimport Darwin\n#else\nimport Glibc\n#endif\nimport Foundation\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n\n@main\nenum CodexBarCLI {\n    static func main() async {\n        let rawArgv = Array(CommandLine.arguments.dropFirst())\n        let argv = Self.effectiveArgv(rawArgv)\n        let outputPreferences = CLIOutputPreferences.from(argv: argv)\n\n        // Fast path: global help/version before building descriptors.\n        if let helpIndex = argv.firstIndex(where: { $0 == \"-h\" || $0 == \"--help\" }) {\n            let command = helpIndex == 0 ? argv.dropFirst().first : argv.first\n            Self.printHelp(for: command)\n        }\n        if argv.contains(\"-V\") || argv.contains(\"--version\") {\n            Self.printVersion()\n        }\n\n        let program = Program(descriptors: Self.commandDescriptors())\n\n        do {\n            let invocation = try program.resolve(argv: argv)\n            Self.bootstrapLogging(values: invocation.parsedValues)\n            switch invocation.path {\n            case [\"usage\"]:\n                await self.runUsage(invocation.parsedValues)\n            case [\"cost\"]:\n                await self.runCost(invocation.parsedValues)\n            case [\"config\", \"validate\"]:\n                self.runConfigValidate(invocation.parsedValues)\n            case [\"config\", \"dump\"]:\n                self.runConfigDump(invocation.parsedValues)\n            default:\n                Self.exit(\n                    code: .failure,\n                    message: \"Unknown command\",\n                    output: outputPreferences,\n                    kind: .args)\n            }\n        } catch let error as CommanderProgramError {\n            Self.exit(code: .failure, message: error.description, output: outputPreferences, kind: .args)\n        } catch {\n            Self.exit(code: .failure, message: error.localizedDescription, output: outputPreferences, kind: .runtime)\n        }\n    }\n\n    private static func commandDescriptors() -> [CommandDescriptor] {\n        let usageSignature = CommandSignature.describe(UsageOptions())\n        let costSignature = CommandSignature.describe(CostOptions())\n        let configSignature = CommandSignature.describe(ConfigOptions())\n\n        return [\n            CommandDescriptor(\n                name: \"usage\",\n                abstract: \"Print usage as text or JSON\",\n                discussion: nil,\n                signature: usageSignature),\n            CommandDescriptor(\n                name: \"cost\",\n                abstract: \"Print local cost usage as text or JSON\",\n                discussion: nil,\n                signature: costSignature),\n            CommandDescriptor(\n                name: \"config\",\n                abstract: \"Config utilities\",\n                discussion: nil,\n                signature: CommandSignature(),\n                subcommands: [\n                    CommandDescriptor(\n                        name: \"validate\",\n                        abstract: \"Validate config file\",\n                        discussion: nil,\n                        signature: configSignature),\n                    CommandDescriptor(\n                        name: \"dump\",\n                        abstract: \"Print normalized config JSON\",\n                        discussion: nil,\n                        signature: configSignature),\n                ],\n                defaultSubcommandName: \"validate\"),\n        ]\n    }\n\n    // MARK: - Helpers\n\n    private static func bootstrapLogging(values: ParsedValues) {\n        let isJSON = values.flags.contains(\"jsonOutput\") || values.flags.contains(\"jsonOnly\")\n        let verbose = values.flags.contains(\"verbose\")\n        let rawLevel = values.options[\"logLevel\"]?.last\n        let level = Self.resolvedLogLevel(verbose: verbose, rawLevel: rawLevel)\n        CodexBarLog.bootstrapIfNeeded(.init(destination: .stderr, level: level, json: isJSON))\n    }\n\n    static func resolvedLogLevel(verbose: Bool, rawLevel: String?) -> CodexBarLog.Level {\n        CodexBarLog.parseLevel(rawLevel) ?? (verbose ? .debug : .error)\n    }\n\n    static func effectiveArgv(_ argv: [String]) -> [String] {\n        guard let first = argv.first else { return [\"usage\"] }\n        if first.hasPrefix(\"-\") { return [\"usage\"] + argv }\n        return argv\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCLI/CLIErrorReporting.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nenum CLIErrorKind: String, Encodable {\n    case args\n    case config\n    case provider\n    case runtime\n}\n\nstruct ProviderErrorPayload: Encodable {\n    let code: Int32\n    let message: String\n    let kind: CLIErrorKind?\n}\n\nextension CodexBarCLI {\n    static func makeErrorPayload(_ error: Error, kind: CLIErrorKind? = nil) -> ProviderErrorPayload {\n        ProviderErrorPayload(\n            code: self.mapError(error).rawValue,\n            message: error.localizedDescription,\n            kind: kind)\n    }\n\n    static func makeErrorPayload(code: ExitCode, message: String, kind: CLIErrorKind? = nil) -> ProviderErrorPayload {\n        ProviderErrorPayload(code: code.rawValue, message: message, kind: kind)\n    }\n\n    static func makeCLIErrorPayload(\n        message: String,\n        code: ExitCode,\n        kind: CLIErrorKind,\n        pretty: Bool) -> String?\n    {\n        let payload = ProviderPayload(\n            providerID: \"cli\",\n            account: nil,\n            version: nil,\n            source: \"cli\",\n            status: nil,\n            usage: nil,\n            credits: nil,\n            antigravityPlanInfo: nil,\n            openaiDashboard: nil,\n            error: ProviderErrorPayload(code: code.rawValue, message: message, kind: kind))\n        return self.encodeJSON([payload], pretty: pretty)\n    }\n\n    static func makeProviderErrorPayload(\n        provider: UsageProvider,\n        account: String?,\n        source: String,\n        status: ProviderStatusPayload?,\n        error: Error,\n        kind: CLIErrorKind = .provider) -> ProviderPayload\n    {\n        ProviderPayload(\n            provider: provider,\n            account: account,\n            version: nil,\n            source: source,\n            status: status,\n            usage: nil,\n            credits: nil,\n            antigravityPlanInfo: nil,\n            openaiDashboard: nil,\n            error: self.makeErrorPayload(error, kind: kind))\n    }\n\n    static func encodeJSON(_ payload: some Encodable, pretty: Bool) -> String? {\n        let encoder = JSONEncoder()\n        encoder.dateEncodingStrategy = .iso8601\n        encoder.outputFormatting = pretty ? [.prettyPrinted, .sortedKeys] : []\n        guard let data = try? encoder.encode(payload) else { return nil }\n        return String(data: data, encoding: .utf8)\n    }\n\n    static func printJSON(_ payload: some Encodable, pretty: Bool) {\n        if let output = self.encodeJSON(payload, pretty: pretty) {\n            print(output)\n        }\n    }\n\n    static func exit(\n        code: ExitCode,\n        message: String? = nil,\n        output: CLIOutputPreferences? = nil,\n        kind: CLIErrorKind = .runtime) -> Never\n    {\n        if code != .success {\n            if let output, output.usesJSONOutput {\n                let payload = self.makeCLIErrorPayload(\n                    message: message ?? \"Error\",\n                    code: code,\n                    kind: kind,\n                    pretty: output.pretty)\n                if let payload {\n                    print(payload)\n                }\n            } else if let message {\n                self.writeStderr(\"\\(message)\\n\")\n            }\n        }\n        platformExit(code.rawValue)\n    }\n\n    static func printError(_ error: Error, output: CLIOutputPreferences, kind: CLIErrorKind = .runtime) {\n        if output.usesJSONOutput {\n            let payload = ProviderPayload(\n                providerID: \"cli\",\n                account: nil,\n                version: nil,\n                source: \"cli\",\n                status: nil,\n                usage: nil,\n                credits: nil,\n                antigravityPlanInfo: nil,\n                openaiDashboard: nil,\n                error: self.makeErrorPayload(error, kind: kind))\n            self.printJSON([payload], pretty: output.pretty)\n        } else {\n            self.writeStderr(\"Error: \\(error.localizedDescription)\\n\")\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCLI/CLIExitCode.swift",
    "content": "enum ExitCode: Int32 {\n    case success = 0\n    case failure = 1\n    case binaryNotFound = 2\n    case parseError = 3\n    case timeout = 4\n\n    init(_ rawValue: Int) {\n        self = ExitCode(rawValue: Int32(rawValue)) ?? .failure\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCLI/CLIHelp.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nextension CodexBarCLI {\n    static func usageHelp(version: String) -> String {\n        \"\"\"\n        CodexBar \\(version)\n\n        Usage:\n          codexbar usage [--format text|json]\n                       [--json]\n                       [--json-only]\n                       [--json-output] [--log-level <trace|verbose|debug|info|warning|error|critical>] [-v|--verbose]\n                       [--provider \\(ProviderHelp.list)]\n                       [--account <label>] [--account-index <index>] [--all-accounts]\n                       [--no-credits] [--no-color] [--pretty] [--status] [--source <auto|web|cli|oauth|api>]\n                       [--web-timeout <seconds>] [--web-debug-dump-html] [--antigravity-plan-debug] [--augment-debug]\n\n        Description:\n          Print usage from enabled providers as text (default) or JSON. Honors your in-app toggles.\n          Output format: use --json (or --format json) for JSON on stdout; use --json-output for JSON logs on stderr.\n          Source behavior is provider-specific:\n          - Codex: OpenAI web dashboard (usage limits, credits remaining, code review remaining, usage breakdown).\n            Auto falls back to Codex CLI only when cookies are missing.\n          - Claude: claude.ai API.\n            Auto falls back to Claude CLI only when cookies are missing.\n          - Kilo: app.kilo.ai API.\n            Auto falls back to Kilo CLI when API credentials are missing or unauthorized.\n          Token accounts are loaded from ~/.codexbar/config.json.\n          Use --account or --account-index to select a specific token account, or --all-accounts to fetch all.\n          Account selection requires a single provider.\n\n        Global flags:\n          -h, --help      Show help\n          -V, --version   Show version\n          -v, --verbose   Enable verbose logging\n          --no-color      Disable ANSI colors in text output\n          --log-level <trace|verbose|debug|info|warning|error|critical>\n          --json-output   Emit machine-readable logs (JSONL) to stderr\n\n        Examples:\n          codexbar usage\n          codexbar usage --provider claude\n          codexbar usage --provider gemini\n          codexbar usage --format json --provider all --pretty\n          codexbar usage --provider all --json\n          codexbar usage --status\n          codexbar usage --provider codex --source web --format json --pretty\n        \"\"\"\n    }\n\n    static func costHelp(version: String) -> String {\n        \"\"\"\n        CodexBar \\(version)\n\n        Usage:\n          codexbar cost [--format text|json]\n                       [--json]\n                       [--json-only]\n                       [--json-output] [--log-level <trace|verbose|debug|info|warning|error|critical>] [-v|--verbose]\n                       [--provider \\(ProviderHelp.list)]\n                       [--no-color] [--pretty] [--refresh]\n\n        Description:\n          Print local token cost usage from Claude/Codex JSONL logs. This does not require web or CLI access.\n          Uses cached scan results unless --refresh is provided.\n\n        Examples:\n          codexbar cost\n          codexbar cost --provider claude --format json --pretty\n        \"\"\"\n    }\n\n    static func configHelp(version: String) -> String {\n        \"\"\"\n        CodexBar \\(version)\n\n        Usage:\n          codexbar config validate [--format text|json]\n                                 [--json]\n                                 [--json-only]\n                                 [--json-output] [--log-level <trace|verbose|debug|info|warning|error|critical>]\n                                 [-v|--verbose]\n                                 [--pretty]\n          codexbar config dump [--format text|json]\n                             [--json]\n                             [--json-only]\n                             [--json-output] [--log-level <trace|verbose|debug|info|warning|error|critical>]\n                             [-v|--verbose]\n                             [--pretty]\n\n        Description:\n          Validate or print the CodexBar config file (default: validate).\n\n        Examples:\n          codexbar config validate --format json --pretty\n          codexbar config dump --pretty\n        \"\"\"\n    }\n\n    static func rootHelp(version: String) -> String {\n        \"\"\"\n        CodexBar \\(version)\n\n        Usage:\n          codexbar [--format text|json]\n                  [--json]\n                  [--json-only]\n                  [--json-output] [--log-level <trace|verbose|debug|info|warning|error|critical>] [-v|--verbose]\n                  [--provider \\(ProviderHelp.list)]\n                  [--account <label>] [--account-index <index>] [--all-accounts]\n                  [--no-credits] [--no-color] [--pretty] [--status] [--source <auto|web|cli|oauth|api>]\n                  [--web-timeout <seconds>] [--web-debug-dump-html] [--antigravity-plan-debug] [--augment-debug]\n          codexbar cost [--format text|json]\n                       [--json]\n                       [--json-only]\n                       [--json-output] [--log-level <trace|verbose|debug|info|warning|error|critical>] [-v|--verbose]\n                       [--provider \\(ProviderHelp.list)] [--no-color] [--pretty] [--refresh]\n          codexbar config <validate|dump> [--format text|json]\n                                        [--json]\n                                        [--json-only]\n                                        [--json-output] [--log-level <trace|verbose|debug|info|warning|error|critical>]\n                                        [-v|--verbose]\n                                        [--pretty]\n\n        Global flags:\n          -h, --help      Show help\n          -V, --version   Show version\n          -v, --verbose   Enable verbose logging\n          --no-color      Disable ANSI colors in text output\n          --log-level <trace|verbose|debug|info|warning|error|critical>\n          --json-output   Emit machine-readable logs (JSONL) to stderr\n\n        Examples:\n          codexbar\n          codexbar --format json --provider all --pretty\n          codexbar --provider all --json\n          codexbar --provider gemini\n          codexbar cost --provider claude --format json --pretty\n          codexbar config validate --format json --pretty\n        \"\"\"\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCLI/CLIHelpers.swift",
    "content": "import CodexBarCore\nimport Commander\n#if canImport(Darwin)\nimport Darwin\n#else\nimport Glibc\n#endif\nimport Foundation\n\nextension CodexBarCLI {\n    static func decodeProvider(from values: ParsedValues, config: CodexBarConfig) -> ProviderSelection {\n        let rawOverride = values.options[\"provider\"]?.last\n        return Self.providerSelection(rawOverride: rawOverride, enabled: config.enabledProviders())\n    }\n\n    static func providerSelection(rawOverride: String?, enabled: [UsageProvider]) -> ProviderSelection {\n        if let rawOverride, let parsed = ProviderSelection(argument: rawOverride) {\n            return parsed\n        }\n        if enabled.count >= 3 { return .all }\n        if enabled.count == 2 {\n            let enabledSet = Set(enabled)\n            let primary = Set(ProviderDescriptorRegistry.all.filter(\\ .metadata.isPrimaryProvider).map(\\ .id))\n            if !primary.isEmpty, enabledSet == primary {\n                return .both\n            }\n            return .custom(enabled)\n        }\n        if let first = enabled.first { return ProviderSelection(provider: first) }\n        return .single(.codex)\n    }\n\n    static func decodeFormat(from values: ParsedValues) -> OutputFormat {\n        if let raw = values.options[\"format\"]?.last, let parsed = OutputFormat(argument: raw) {\n            return parsed\n        }\n        if values.flags.contains(\"jsonShortcut\") || values.flags.contains(\"json\") || values.flags.contains(\"jsonOnly\") {\n            return .json\n        }\n        return .text\n    }\n\n    static func decodeTokenAccountSelection(from values: ParsedValues) throws -> TokenAccountCLISelection {\n        let label = values.options[\"account\"]?.last\n        let rawIndex = values.options[\"accountIndex\"]?.last\n        var index: Int?\n        if let rawIndex {\n            guard let parsed = Int(rawIndex), parsed > 0 else {\n                throw CLIArgumentError(\"--account-index must be a positive integer.\")\n            }\n            index = parsed - 1\n        }\n        let allAccounts = values.flags.contains(\"allAccounts\")\n        return TokenAccountCLISelection(label: label, index: index, allAccounts: allAccounts)\n    }\n\n    static func shouldUseColor(noColor: Bool, format: OutputFormat) -> Bool {\n        guard format == .text else { return false }\n        if noColor { return false }\n        let env = ProcessInfo.processInfo.environment\n        if env[\"TERM\"]?.lowercased() == \"dumb\" { return false }\n        return isatty(STDOUT_FILENO) == 1\n    }\n\n    static func detectVersion(for provider: UsageProvider, browserDetection: BrowserDetection) -> String? {\n        ProviderDescriptorRegistry.descriptor(for: provider).cli.versionDetector?(browserDetection)\n    }\n\n    static func normalizeVersion(raw: String?) -> String? {\n        guard let raw, !raw.isEmpty else { return nil }\n        if let match = raw.range(of: #\"(\\d+(?:\\.\\d+)+)\"#, options: .regularExpression) {\n            return String(raw[match]).trimmingCharacters(in: .whitespacesAndNewlines)\n        }\n        return raw.trimmingCharacters(in: .whitespacesAndNewlines)\n    }\n\n    static func makeHeader(provider: UsageProvider, version: String?, source: String) -> String {\n        let name = ProviderDescriptorRegistry.descriptor(for: provider).metadata.displayName\n        if let version, !version.isEmpty {\n            return \"\\(name) \\(version) (\\(source))\"\n        }\n        return \"\\(name) (\\(source))\"\n    }\n\n    static func printFetchAttempts(provider: UsageProvider, attempts: [ProviderFetchAttempt]) {\n        guard !attempts.isEmpty else { return }\n        self.writeStderr(\"[\\(provider.rawValue)] fetch strategies:\\n\")\n        for attempt in attempts {\n            let kindLabel = Self.fetchKindLabel(attempt.kind)\n            var line = \"  - \\(attempt.strategyID) (\\(kindLabel))\"\n            line += attempt.wasAvailable ? \" available\" : \" unavailable\"\n            if let error = attempt.errorDescription, !error.isEmpty {\n                line += \" error=\\(error)\"\n            }\n            self.writeStderr(\"\\(line)\\n\")\n        }\n    }\n\n    static func usageTextNotes(\n        provider: UsageProvider,\n        sourceMode: ProviderSourceMode,\n        resolvedSourceLabel: String) -> [String]\n    {\n        guard provider == .kilo,\n              sourceMode == .auto,\n              resolvedSourceLabel.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() == \"cli\"\n        else {\n            return []\n        }\n        return [\"Using CLI fallback\"]\n    }\n\n    static func kiloAutoFallbackSummary(\n        provider: UsageProvider,\n        sourceMode: ProviderSourceMode,\n        attempts: [ProviderFetchAttempt]) -> String?\n    {\n        guard provider == .kilo, sourceMode == .auto, !attempts.isEmpty else { return nil }\n        let parts = attempts.map { attempt in\n            let label = Self.fetchKindLabel(attempt.kind)\n            let message = attempt.errorDescription?.trimmingCharacters(in: .whitespacesAndNewlines) ?? \"\"\n            if !message.isEmpty {\n                return \"\\(label): \\(message)\"\n            }\n            return \"\\(label): \\(attempt.wasAvailable ? \"success\" : \"unavailable\")\"\n        }\n        guard !parts.isEmpty else { return nil }\n        return \"Kilo auto fallback attempts: \" + parts.joined(separator: \" -> \")\n    }\n\n    private static func fetchKindLabel(_ kind: ProviderFetchKind) -> String {\n        switch kind {\n        case .cli: \"cli\"\n        case .web: \"web\"\n        case .oauth: \"oauth\"\n        case .apiToken: \"api\"\n        case .localProbe: \"local\"\n        case .webDashboard: \"web\"\n        }\n    }\n\n    static func fetchStatus(for provider: UsageProvider) async -> ProviderStatusPayload? {\n        let urlString = ProviderDescriptorRegistry.descriptor(for: provider).metadata.statusPageURL\n        guard let urlString,\n              let baseURL = URL(string: urlString) else { return nil }\n        do {\n            return try await StatusFetcher.fetch(from: baseURL)\n        } catch {\n            return ProviderStatusPayload(\n                indicator: .unknown,\n                description: error.localizedDescription,\n                updatedAt: nil,\n                url: urlString)\n        }\n    }\n\n    static func resetTimeDisplayStyleFromDefaults() -> ResetTimeDisplayStyle {\n        let domains = [\n            \"com.steipete.codexbar\",\n            \"com.steipete.codexbar.debug\",\n        ]\n        for domain in domains {\n            if let value = UserDefaults(suiteName: domain)?.object(forKey: \"resetTimesShowAbsolute\") as? Bool {\n                return value ? .absolute : .countdown\n            }\n        }\n        let fallback = UserDefaults.standard.object(forKey: \"resetTimesShowAbsolute\") as? Bool ?? false\n        return fallback ? .absolute : .countdown\n    }\n\n    static func fetchProviderUsage(\n        provider: UsageProvider,\n        context: ProviderFetchContext) async -> ProviderFetchOutcome\n    {\n        let descriptor = ProviderDescriptorRegistry.descriptor(for: provider)\n        if !descriptor.fetchPlan.sourceModes.contains(context.sourceMode) {\n            let error = SourceSelectionError.unsupported(\n                provider: descriptor.cli.name,\n                source: context.sourceMode)\n            return ProviderFetchOutcome(result: .failure(error), attempts: [])\n        }\n        return await descriptor.fetchOutcome(context: context)\n    }\n\n    private enum SourceSelectionError: LocalizedError {\n        case unsupported(provider: String, source: ProviderSourceMode)\n\n        var errorDescription: String? {\n            switch self {\n            case let .unsupported(provider, source):\n                \"Source '\\(source.rawValue)' is not supported for \\(provider).\"\n            }\n        }\n    }\n\n    static func loadOpenAIDashboardIfAvailable(\n        usage: UsageSnapshot,\n        fetcher: UsageFetcher) -> OpenAIDashboardSnapshot?\n    {\n        guard let cache = OpenAIDashboardCacheStore.load() else { return nil }\n        let codexEmail = (usage.accountEmail(for: .codex) ?? fetcher.loadAccountInfo().email)?\n            .trimmingCharacters(in: .whitespacesAndNewlines)\n        guard let codexEmail, !codexEmail.isEmpty else { return nil }\n        if cache.accountEmail.lowercased() != codexEmail.lowercased() { return nil }\n        if cache.snapshot.dailyBreakdown.isEmpty, !cache.snapshot.creditEvents.isEmpty {\n            return OpenAIDashboardSnapshot(\n                signedInEmail: cache.snapshot.signedInEmail,\n                codeReviewRemainingPercent: cache.snapshot.codeReviewRemainingPercent,\n                creditEvents: cache.snapshot.creditEvents,\n                dailyBreakdown: OpenAIDashboardSnapshot.makeDailyBreakdown(\n                    from: cache.snapshot.creditEvents,\n                    maxDays: 30),\n                usageBreakdown: cache.snapshot.usageBreakdown,\n                creditsPurchaseURL: cache.snapshot.creditsPurchaseURL,\n                updatedAt: cache.snapshot.updatedAt)\n        }\n        return cache.snapshot\n    }\n\n    static func decodeWebTimeout(from values: ParsedValues) -> TimeInterval? {\n        if let raw = values.options[\"webTimeout\"]?.last, let seconds = Double(raw) {\n            return seconds\n        }\n        return nil\n    }\n\n    static func decodeSourceMode(from values: ParsedValues) -> ProviderSourceMode? {\n        if values.flags.contains(\"web\") {\n            return .web\n        }\n        guard let raw = values.options[\"source\"]?.last?.lowercased() else { return nil }\n        return ProviderSourceMode(rawValue: raw)\n    }\n\n    static func renderOpenAIWebDashboardText(_ dash: OpenAIDashboardSnapshot) -> String {\n        var lines: [String] = []\n        if let email = dash.signedInEmail, !email.isEmpty {\n            lines.append(\"Web session: \\(email)\")\n        }\n        if let remaining = dash.codeReviewRemainingPercent {\n            let percent = Int(remaining.rounded())\n            lines.append(\"Code review: \\(percent)% remaining\")\n        }\n        if let first = dash.creditEvents.first {\n            let day = first.date.formatted(date: .abbreviated, time: .omitted)\n            lines.append(\"Web history: \\(dash.creditEvents.count) events (latest \\(day))\")\n        } else {\n            lines.append(\"Web history: none\")\n        }\n        return lines.joined(separator: \"\\n\")\n    }\n\n    static func mapError(_ error: Error) -> ExitCode {\n        switch error {\n        case TTYCommandRunner.Error.binaryNotFound,\n             CodexStatusProbeError.codexNotInstalled,\n             ClaudeUsageError.claudeNotInstalled,\n             GeminiStatusProbeError.geminiNotInstalled:\n            ExitCode(2)\n        case CodexStatusProbeError.timedOut,\n             TTYCommandRunner.Error.timedOut,\n             GeminiStatusProbeError.timedOut,\n             CostUsageError.timedOut:\n            ExitCode(4)\n        case ClaudeUsageError.parseFailed,\n             ClaudeUsageError.oauthFailed,\n             CostUsageError.unsupportedProvider,\n             UsageError.decodeFailed,\n             UsageError.noRateLimitsFound,\n             GeminiStatusProbeError.parseFailed:\n            ExitCode(3)\n        default:\n            .failure\n        }\n    }\n\n    static func printAntigravityPlanInfo(_ info: AntigravityPlanInfoSummary) {\n        let fields: [(String, String?)] = [\n            (\"planName\", info.planName),\n            (\"planDisplayName\", info.planDisplayName),\n            (\"displayName\", info.displayName),\n            (\"productName\", info.productName),\n            (\"planShortName\", info.planShortName),\n        ]\n        self.writeStderr(\"Antigravity plan info:\\n\")\n        for (label, value) in fields {\n            guard let value, !value.isEmpty else { continue }\n            self.writeStderr(\"  \\(label): \\(value)\\n\")\n        }\n    }\n\n    static func loadConfig(output: CLIOutputPreferences) -> CodexBarConfig {\n        let store = CodexBarConfigStore()\n        do {\n            if let existing = try store.load() {\n                return existing\n            }\n            return CodexBarConfig.makeDefault()\n        } catch {\n            if output.usesJSONOutput {\n                let payload = ProviderPayload(\n                    providerID: \"cli\",\n                    account: nil,\n                    version: nil,\n                    source: \"cli\",\n                    status: nil,\n                    usage: nil,\n                    credits: nil,\n                    antigravityPlanInfo: nil,\n                    openaiDashboard: nil,\n                    error: self.makeErrorPayload(code: .failure, message: error.localizedDescription, kind: .config))\n                self.printJSON([payload], pretty: output.pretty)\n            } else {\n                self.writeStderr(\"Error: \\(error.localizedDescription)\\n\")\n            }\n            Self.platformExit(ExitCode.failure.rawValue)\n        }\n    }\n}\n\nstruct CLIArgumentError: LocalizedError {\n    let message: String\n\n    init(_ message: String) {\n        self.message = message\n    }\n\n    var errorDescription: String? {\n        self.message\n    }\n}\n\n#if DEBUG\nextension CodexBarCLI {\n    static func _usageSignatureForTesting() -> CommandSignature {\n        CommandSignature.describe(UsageOptions())\n    }\n\n    static func _costSignatureForTesting() -> CommandSignature {\n        CommandSignature.describe(CostOptions())\n    }\n\n    static func _decodeFormatForTesting(from values: ParsedValues) -> OutputFormat {\n        self.decodeFormat(from: values)\n    }\n\n    static func _decodeWebTimeoutForTesting(from values: ParsedValues) -> TimeInterval? {\n        self.decodeWebTimeout(from: values)\n    }\n\n    static func _decodeSourceModeForTesting(from values: ParsedValues) -> ProviderSourceMode? {\n        self.decodeSourceMode(from: values)\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/CodexBarCLI/CLIIO.swift",
    "content": "#if canImport(Darwin)\nimport Darwin\n#else\nimport Glibc\n#endif\nimport Foundation\n\nextension CodexBarCLI {\n    static func writeStderr(_ string: String) {\n        guard let data = string.data(using: .utf8) else { return }\n        FileHandle.standardError.write(data)\n    }\n\n    static func printVersion() -> Never {\n        if let version = Bundle.main.infoDictionary?[\"CFBundleShortVersionString\"] as? String {\n            print(\"CodexBar \\(version)\")\n        } else {\n            print(\"CodexBar\")\n        }\n        Self.platformExit(0)\n    }\n\n    static func printHelp(for command: String?) -> Never {\n        let version = Bundle.main.infoDictionary?[\"CFBundleShortVersionString\"] as? String ?? \"unknown\"\n        switch command {\n        case \"usage\":\n            print(Self.usageHelp(version: version))\n        case \"cost\":\n            print(Self.costHelp(version: version))\n        case \"config\", \"validate\", \"dump\":\n            print(Self.configHelp(version: version))\n        default:\n            print(Self.rootHelp(version: version))\n        }\n        Self.platformExit(0)\n    }\n\n    static func platformExit(_ code: Int32) -> Never {\n        #if canImport(Darwin)\n        Darwin.exit(code)\n        #else\n        Glibc.exit(code)\n        #endif\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCLI/CLIOptions.swift",
    "content": "import CodexBarCore\nimport Commander\nimport Foundation\n\n// MARK: - Options & parsing helpers\n\nstruct UsageOptions: CommanderParsable {\n    private static let sourceHelp: String = {\n        #if os(macOS)\n        \"Data source: auto | web | cli | oauth | api (auto behavior is provider-specific)\"\n        #else\n        \"Data source: auto | web | cli | oauth | api (web/auto are macOS only for web-capable providers)\"\n        #endif\n    }()\n\n    @Flag(names: [.short(\"v\"), .long(\"verbose\")], help: \"Enable verbose logging\")\n    var verbose: Bool = false\n\n    @Flag(name: .long(\"json-output\"), help: \"Emit machine-readable logs\")\n    var jsonOutput: Bool = false\n\n    @Option(name: .long(\"log-level\"), help: \"Set log level (trace|verbose|debug|info|warning|error|critical)\")\n    var logLevel: String?\n\n    @Option(\n        name: .long(\"provider\"),\n        help: ProviderHelp.optionHelp)\n    var provider: ProviderSelection?\n\n    @Option(name: .long(\"account\"), help: \"Token account label to use (from config.json)\")\n    var account: String?\n\n    @Option(name: .long(\"account-index\"), help: \"Token account index (1-based)\")\n    var accountIndex: Int?\n\n    @Flag(name: .long(\"all-accounts\"), help: \"Fetch all token accounts for the provider\")\n    var allAccounts: Bool = false\n\n    @Option(name: .long(\"format\"), help: \"Output format: text | json\")\n    var format: OutputFormat?\n\n    @Flag(name: .long(\"json\"), help: \"\")\n    var jsonShortcut: Bool = false\n\n    @Flag(name: .long(\"json-only\"), help: \"Emit JSON only (suppress non-JSON output)\")\n    var jsonOnly: Bool = false\n\n    @Flag(name: .long(\"no-credits\"), help: \"Skip Codex credits line\")\n    var noCredits: Bool = false\n\n    @Flag(name: .long(\"no-color\"), help: \"Disable ANSI colors in text output\")\n    var noColor: Bool = false\n\n    @Flag(name: .long(\"pretty\"), help: \"Pretty-print JSON output\")\n    var pretty: Bool = false\n\n    @Flag(name: .long(\"status\"), help: \"Fetch and include provider status\")\n    var status: Bool = false\n\n    @Flag(name: .long(\"web\"), help: \"Alias for --source web\")\n    var web: Bool = false\n\n    @Option(name: .long(\"source\"), help: Self.sourceHelp)\n    var source: String?\n\n    @Option(name: .long(\"web-timeout\"), help: \"Web fetch timeout (seconds) (Codex only; source=auto|web)\")\n    var webTimeout: Double?\n\n    @Flag(name: .long(\"web-debug-dump-html\"), help: \"Dump HTML snapshots to /tmp when Codex dashboard data is missing\")\n    var webDebugDumpHtml: Bool = false\n\n    @Flag(name: .long(\"antigravity-plan-debug\"), help: \"Emit Antigravity planInfo fields (debug)\")\n    var antigravityPlanDebug: Bool = false\n\n    @Flag(name: .long(\"augment-debug\"), help: \"Emit Augment API responses (debug)\")\n    var augmentDebug: Bool = false\n}\n\nenum ProviderSelection: ExpressibleFromArgument {\n    case single(UsageProvider)\n    case both\n    case all\n    case custom([UsageProvider])\n\n    init?(argument: String) {\n        let normalized = argument.lowercased()\n        switch normalized {\n        case \"both\":\n            self = .both\n        case \"all\":\n            self = .all\n        default:\n            if let provider = ProviderDescriptorRegistry.cliNameMap[normalized] {\n                self = .single(provider)\n            } else {\n                return nil\n            }\n        }\n    }\n\n    init(provider: UsageProvider) {\n        self = .single(provider)\n    }\n\n    var asList: [UsageProvider] {\n        switch self {\n        case let .single(provider):\n            return [provider]\n        case .both:\n            let primary = ProviderDescriptorRegistry.all.filter(\\ .metadata.isPrimaryProvider)\n            if !primary.isEmpty {\n                return primary.map(\\ .id)\n            }\n            return ProviderDescriptorRegistry.all.prefix(2).map(\\ .id)\n        case .all:\n            return ProviderDescriptorRegistry.all.map(\\ .id)\n        case let .custom(providers):\n            return providers\n        }\n    }\n}\n\nenum OutputFormat: String, ExpressibleFromArgument {\n    case text\n    case json\n\n    init?(argument: String) {\n        switch argument.lowercased() {\n        case \"text\": self = .text\n        case \"json\": self = .json\n        default: return nil\n        }\n    }\n}\n\nenum ProviderHelp {\n    static var list: String {\n        let names = ProviderDescriptorRegistry.all.map(\\ .cli.name)\n        return (names + [\"both\", \"all\"]).joined(separator: \"|\")\n    }\n\n    static var optionHelp: String {\n        \"Provider to query: \\(self.list)\"\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCLI/CLIOutputPreferences.swift",
    "content": "import Commander\nimport Foundation\n\nstruct CLIOutputPreferences {\n    let format: OutputFormat\n    let jsonOnly: Bool\n    let pretty: Bool\n\n    var usesJSONOutput: Bool {\n        self.jsonOnly || self.format == .json\n    }\n\n    static func from(values: ParsedValues) -> CLIOutputPreferences {\n        let jsonOnly = values.flags.contains(\"jsonOnly\")\n        let format = CodexBarCLI.decodeFormat(from: values)\n        let pretty = values.flags.contains(\"pretty\")\n        return CLIOutputPreferences(format: format, jsonOnly: jsonOnly, pretty: pretty)\n    }\n\n    static func from(argv: [String]) -> CLIOutputPreferences {\n        var jsonOnly = false\n        var pretty = false\n        var format: OutputFormat = .text\n\n        var index = 0\n        while index < argv.count {\n            let arg = argv[index]\n            switch arg {\n            case \"--json-only\":\n                jsonOnly = true\n                format = .json\n            case \"--json\":\n                format = .json\n            case \"--pretty\":\n                pretty = true\n            case \"--format\":\n                let next = index + 1\n                if next < argv.count, let parsed = OutputFormat(argument: argv[next]) {\n                    format = parsed\n                    index += 1\n                }\n            default:\n                break\n            }\n            index += 1\n        }\n\n        return CLIOutputPreferences(format: format, jsonOnly: jsonOnly, pretty: pretty)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCLI/CLIPayloads.swift",
    "content": "import CodexBarCore\nimport Foundation\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n\nstruct ProviderPayload: Encodable {\n    let provider: String\n    let account: String?\n    let version: String?\n    let source: String\n    let status: ProviderStatusPayload?\n    let usage: UsageSnapshot?\n    let credits: CreditsSnapshot?\n    let antigravityPlanInfo: AntigravityPlanInfoSummary?\n    let openaiDashboard: OpenAIDashboardSnapshot?\n    let error: ProviderErrorPayload?\n\n    init(\n        provider: UsageProvider,\n        account: String?,\n        version: String?,\n        source: String,\n        status: ProviderStatusPayload?,\n        usage: UsageSnapshot?,\n        credits: CreditsSnapshot?,\n        antigravityPlanInfo: AntigravityPlanInfoSummary?,\n        openaiDashboard: OpenAIDashboardSnapshot?,\n        error: ProviderErrorPayload?)\n    {\n        self.provider = provider.rawValue\n        self.account = account\n        self.version = version\n        self.source = source\n        self.status = status\n        self.usage = usage\n        self.credits = credits\n        self.antigravityPlanInfo = antigravityPlanInfo\n        self.openaiDashboard = openaiDashboard\n        self.error = error\n    }\n\n    init(\n        providerID: String,\n        account: String?,\n        version: String?,\n        source: String,\n        status: ProviderStatusPayload?,\n        usage: UsageSnapshot?,\n        credits: CreditsSnapshot?,\n        antigravityPlanInfo: AntigravityPlanInfoSummary?,\n        openaiDashboard: OpenAIDashboardSnapshot?,\n        error: ProviderErrorPayload?)\n    {\n        self.provider = providerID\n        self.account = account\n        self.version = version\n        self.source = source\n        self.status = status\n        self.usage = usage\n        self.credits = credits\n        self.antigravityPlanInfo = antigravityPlanInfo\n        self.openaiDashboard = openaiDashboard\n        self.error = error\n    }\n}\n\nstruct ProviderStatusPayload: Encodable {\n    let indicator: ProviderStatusIndicator\n    let description: String?\n    let updatedAt: Date?\n    let url: String\n\n    enum ProviderStatusIndicator: String, Encodable {\n        case none\n        case minor\n        case major\n        case critical\n        case maintenance\n        case unknown\n\n        var label: String {\n            switch self {\n            case .none: \"Operational\"\n            case .minor: \"Partial outage\"\n            case .major: \"Major outage\"\n            case .critical: \"Critical issue\"\n            case .maintenance: \"Maintenance\"\n            case .unknown: \"Status unknown\"\n            }\n        }\n    }\n\n    var descriptionSuffix: String {\n        guard let description, !description.isEmpty else { return \"\" }\n        return \" – \\(description)\"\n    }\n}\n\nenum StatusFetcher {\n    static func fetch(from baseURL: URL) async throws -> ProviderStatusPayload {\n        let apiURL = baseURL.appendingPathComponent(\"api/v2/status.json\")\n        var request = URLRequest(url: apiURL)\n        request.timeoutInterval = 10\n\n        let (data, _) = try await URLSession.shared.data(for: request)\n\n        struct Response: Decodable {\n            struct Status: Decodable {\n                let indicator: String\n                let description: String?\n            }\n\n            struct Page: Decodable {\n                let updatedAt: Date?\n\n                private enum CodingKeys: String, CodingKey {\n                    case updatedAt = \"updated_at\"\n                }\n            }\n\n            let page: Page?\n            let status: Status\n        }\n\n        let decoder = JSONDecoder()\n        decoder.dateDecodingStrategy = .custom { decoder in\n            let container = try decoder.singleValueContainer()\n            let raw = try container.decode(String.self)\n            let formatter = ISO8601DateFormatter()\n            formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]\n            if let date = formatter.date(from: raw) { return date }\n            formatter.formatOptions = [.withInternetDateTime]\n            if let date = formatter.date(from: raw) { return date }\n            throw DecodingError.dataCorruptedError(in: container, debugDescription: \"Invalid ISO8601 date\")\n        }\n\n        let response = try decoder.decode(Response.self, from: data)\n        let indicator = ProviderStatusPayload.ProviderStatusIndicator(rawValue: response.status.indicator) ?? .unknown\n        return ProviderStatusPayload(\n            indicator: indicator,\n            description: response.status.description,\n            updatedAt: response.page?.updatedAt,\n            url: baseURL.absoluteString)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCLI/CLIRenderer.swift",
    "content": "import CodexBarCore\nimport Foundation\n\nenum CLIRenderer {\n    private static let accentColor = \"95\"\n    private static let accentBoldColor = \"1;95\"\n    private static let subtleColor = \"90\"\n    private static let paceMinimumExpectedPercent: Double = 3\n    private static let usageBarWidth = 12\n\n    static func renderText(\n        provider: UsageProvider,\n        snapshot: UsageSnapshot,\n        credits: CreditsSnapshot?,\n        context: RenderContext) -> String\n    {\n        let meta = ProviderDescriptorRegistry.descriptor(for: provider).metadata\n        let now = Date()\n        var lines: [String] = []\n        lines.append(self.headerLine(context.header, useColor: context.useColor))\n        self.appendPrimaryLines(\n            provider: provider,\n            snapshot: snapshot,\n            metadata: meta,\n            context: context,\n            now: now,\n            lines: &lines)\n        self.appendSecondaryLines(\n            provider: provider,\n            snapshot: snapshot,\n            metadata: meta,\n            context: context,\n            now: now,\n            lines: &lines)\n        self.appendTertiaryLines(snapshot: snapshot, metadata: meta, context: context, now: now, lines: &lines)\n        self.appendCreditsLine(provider: provider, credits: credits, useColor: context.useColor, lines: &lines)\n        self.appendIdentityAndNotes(\n            provider: provider,\n            snapshot: snapshot,\n            context: context,\n            lines: &lines)\n\n        if let status = context.status {\n            let statusLine = \"Status: \\(status.indicator.label)\\(status.descriptionSuffix)\"\n            lines.append(self.colorize(statusLine, indicator: status.indicator, useColor: context.useColor))\n        }\n\n        return lines.joined(separator: \"\\n\")\n    }\n\n    static func rateLine(title: String, window: RateWindow, useColor: Bool) -> String {\n        let text = UsageFormatter.usageLine(\n            remaining: window.remainingPercent,\n            used: window.usedPercent,\n            showUsed: false)\n        let colored = self.colorizeUsage(text, remainingPercent: window.remainingPercent, useColor: useColor)\n        let bar = self.usageBar(remainingPercent: window.remainingPercent, useColor: useColor)\n        return \"\\(title): \\(colored) \\(bar)\"\n    }\n\n    // swiftlint:disable:next function_parameter_count\n    private static func appendPrimaryLines(\n        provider: UsageProvider,\n        snapshot: UsageSnapshot,\n        metadata: ProviderMetadata,\n        context: RenderContext,\n        now: Date,\n        lines: inout [String])\n    {\n        if let primary = snapshot.primary {\n            self.appendRateWindowLines(\n                provider: provider,\n                title: metadata.sessionLabel,\n                window: primary,\n                includePace: false,\n                context: context,\n                now: now,\n                lines: &lines)\n            return\n        }\n\n        guard let cost = snapshot.providerCost else { return }\n        // Fallback to cost/quota display if no primary rate window.\n        let label = cost.currencyCode == \"Quota\" ? \"Quota\" : \"Cost\"\n        let value = \"\\(String(format: \"%.1f\", cost.used)) / \\(String(format: \"%.1f\", cost.limit))\"\n        lines.append(self.labelValueLine(label, value: value, useColor: context.useColor))\n    }\n\n    // swiftlint:disable:next function_parameter_count\n    private static func appendSecondaryLines(\n        provider: UsageProvider,\n        snapshot: UsageSnapshot,\n        metadata: ProviderMetadata,\n        context: RenderContext,\n        now: Date,\n        lines: inout [String])\n    {\n        guard let weekly = snapshot.secondary else { return }\n        self.appendRateWindowLines(\n            provider: provider,\n            title: metadata.weeklyLabel,\n            window: weekly,\n            includePace: true,\n            context: context,\n            now: now,\n            lines: &lines)\n    }\n\n    private static func appendTertiaryLines(\n        snapshot: UsageSnapshot,\n        metadata: ProviderMetadata,\n        context: RenderContext,\n        now: Date,\n        lines: inout [String])\n    {\n        guard metadata.supportsOpus, let opus = snapshot.tertiary else { return }\n        lines.append(self.rateLine(title: metadata.opusLabel ?? \"Sonnet\", window: opus, useColor: context.useColor))\n        if let reset = self.resetLine(for: opus, style: context.resetStyle, now: now) {\n            lines.append(self.subtleLine(reset, useColor: context.useColor))\n        }\n    }\n\n    private static func appendCreditsLine(\n        provider: UsageProvider,\n        credits: CreditsSnapshot?,\n        useColor: Bool,\n        lines: inout [String])\n    {\n        guard provider == .codex, let credits else { return }\n        lines.append(self.labelValueLine(\n            \"Credits\",\n            value: UsageFormatter.creditsString(from: credits.remaining),\n            useColor: useColor))\n    }\n\n    private static func appendIdentityAndNotes(\n        provider: UsageProvider,\n        snapshot: UsageSnapshot,\n        context: RenderContext,\n        lines: inout [String])\n    {\n        if let email = snapshot.accountEmail(for: provider), !email.isEmpty {\n            lines.append(self.labelValueLine(\"Account\", value: email, useColor: context.useColor))\n        }\n\n        if provider == .kilo {\n            let kiloLogin = self.kiloLoginParts(snapshot: snapshot)\n            if let pass = kiloLogin.pass {\n                let cleaned = UsageFormatter.cleanPlanName(pass)\n                lines.append(self.labelValueLine(\"Plan\", value: cleaned, useColor: context.useColor))\n            }\n            for detail in kiloLogin.details {\n                lines.append(self.labelValueLine(\"Activity\", value: detail, useColor: context.useColor))\n            }\n        } else if let plan = snapshot.loginMethod(for: provider), !plan.isEmpty {\n            lines.append(self.labelValueLine(\"Plan\", value: plan.capitalized, useColor: context.useColor))\n        }\n\n        for note in context.notes {\n            let trimmed = note.trimmingCharacters(in: .whitespacesAndNewlines)\n            guard !trimmed.isEmpty else { continue }\n            lines.append(self.labelValueLine(\"Note\", value: trimmed, useColor: context.useColor))\n        }\n    }\n\n    // swiftlint:disable:next function_parameter_count\n    private static func appendRateWindowLines(\n        provider: UsageProvider,\n        title: String,\n        window: RateWindow,\n        includePace: Bool,\n        context: RenderContext,\n        now: Date,\n        lines: inout [String])\n    {\n        lines.append(self.rateLine(title: title, window: window, useColor: context.useColor))\n        if includePace,\n           let pace = self.paceLine(provider: provider, window: window, useColor: context.useColor, now: now)\n        {\n            lines.append(pace)\n        }\n        self.appendResetAndDetailLines(\n            provider: provider,\n            window: window,\n            context: context,\n            now: now,\n            lines: &lines)\n    }\n\n    private static func appendResetAndDetailLines(\n        provider: UsageProvider,\n        window: RateWindow,\n        context: RenderContext,\n        now: Date,\n        lines: inout [String])\n    {\n        if provider == .warp || provider == .kilo {\n            if let reset = self.resetLineForDetailBackedWindow(window: window, style: context.resetStyle, now: now) {\n                lines.append(self.subtleLine(reset, useColor: context.useColor))\n            }\n            if let detail = self.detailLineForDetailBackedWindow(window: window) {\n                lines.append(self.subtleLine(detail, useColor: context.useColor))\n            }\n            return\n        }\n\n        if let reset = self.resetLine(for: window, style: context.resetStyle, now: now) {\n            lines.append(self.subtleLine(reset, useColor: context.useColor))\n        }\n    }\n\n    private static func resetLine(for window: RateWindow, style: ResetTimeDisplayStyle, now: Date) -> String? {\n        UsageFormatter.resetLine(for: window, style: style, now: now)\n    }\n\n    private static func resetLineForDetailBackedWindow(\n        window: RateWindow,\n        style: ResetTimeDisplayStyle,\n        now: Date) -> String?\n    {\n        // Warp/Kilo use resetDescription for non-reset detail.\n        // Only render \"Resets ...\" when a concrete reset date exists.\n        guard window.resetsAt != nil else { return nil }\n        let resetOnlyWindow = RateWindow(\n            usedPercent: window.usedPercent,\n            windowMinutes: window.windowMinutes,\n            resetsAt: window.resetsAt,\n            resetDescription: nil)\n        return UsageFormatter.resetLine(for: resetOnlyWindow, style: style, now: now)\n    }\n\n    private static func detailLineForDetailBackedWindow(window: RateWindow) -> String? {\n        guard let desc = window.resetDescription else { return nil }\n        let trimmed = desc.trimmingCharacters(in: .whitespacesAndNewlines)\n        return trimmed.isEmpty ? nil : trimmed\n    }\n\n    private static func kiloLoginParts(snapshot: UsageSnapshot) -> (pass: String?, details: [String]) {\n        guard let loginMethod = snapshot.loginMethod(for: .kilo) else {\n            return (nil, [])\n        }\n        let parts = loginMethod\n            .components(separatedBy: \"·\")\n            .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }\n            .filter { !$0.isEmpty }\n        guard !parts.isEmpty else {\n            return (nil, [])\n        }\n        let first = parts[0]\n        if self.isKiloActivitySegment(first) {\n            return (nil, parts)\n        }\n        return (first, Array(parts.dropFirst()))\n    }\n\n    private static func isKiloActivitySegment(_ text: String) -> Bool {\n        let normalized = text.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()\n        return normalized.hasPrefix(\"auto top-up:\")\n    }\n\n    private static func headerLine(_ header: String, useColor: Bool) -> String {\n        let decorated = \"== \\(header) ==\"\n        guard useColor else { return decorated }\n        return self.ansi(self.accentBoldColor, decorated)\n    }\n\n    private static func labelValueLine(_ label: String, value: String, useColor: Bool) -> String {\n        let labelText = self.label(label, useColor: useColor)\n        return \"\\(labelText): \\(value)\"\n    }\n\n    private static func label(_ text: String, useColor: Bool) -> String {\n        guard useColor else { return text }\n        return self.ansi(self.accentColor, text)\n    }\n\n    private static func subtleLine(_ text: String, useColor: Bool) -> String {\n        guard useColor else { return text }\n        return self.ansi(self.subtleColor, text)\n    }\n\n    private static func usageBar(remainingPercent: Double, useColor: Bool) -> String {\n        let clamped = max(0, min(100, remainingPercent))\n        let rawFilled = Int((clamped / 100) * Double(Self.usageBarWidth))\n        let filled = max(0, min(Self.usageBarWidth, rawFilled))\n        let empty = max(0, Self.usageBarWidth - filled)\n        let bar = \"[\\(String(repeating: \"=\", count: filled))\\(String(repeating: \"-\", count: empty))]\"\n        guard useColor else { return bar }\n        return self.ansi(self.accentColor, bar)\n    }\n\n    private static func paceLine(\n        provider: UsageProvider,\n        window: RateWindow,\n        useColor: Bool,\n        now: Date) -> String?\n    {\n        guard provider == .codex || provider == .claude else { return nil }\n        guard window.remainingPercent > 0 else { return nil }\n        guard let pace = UsagePace.weekly(window: window, now: now, defaultWindowMinutes: 10080) else { return nil }\n        guard pace.expectedUsedPercent >= Self.paceMinimumExpectedPercent else { return nil }\n\n        let expected = Int(pace.expectedUsedPercent.rounded())\n        var parts: [String] = []\n        parts.append(Self.paceLeftLabel(for: pace))\n        parts.append(\"Expected \\(expected)% used\")\n        if let rightLabel = Self.paceRightLabel(for: pace, now: now) {\n            parts.append(rightLabel)\n        }\n        let label = self.label(\"Pace\", useColor: useColor)\n        return \"\\(label): \\(parts.joined(separator: \" | \"))\"\n    }\n\n    private static func paceLeftLabel(for pace: UsagePace) -> String {\n        let deltaValue = Int(abs(pace.deltaPercent).rounded())\n        switch pace.stage {\n        case .onTrack:\n            return \"On pace\"\n        case .slightlyAhead, .ahead, .farAhead:\n            return \"\\(deltaValue)% in deficit\"\n        case .slightlyBehind, .behind, .farBehind:\n            return \"\\(deltaValue)% in reserve\"\n        }\n    }\n\n    private static func paceRightLabel(for pace: UsagePace, now: Date) -> String? {\n        if pace.willLastToReset { return \"Lasts until reset\" }\n        guard let etaSeconds = pace.etaSeconds else { return nil }\n        let etaText = Self.paceDurationText(seconds: etaSeconds, now: now)\n        if etaText == \"now\" { return \"Runs out now\" }\n        return \"Runs out in \\(etaText)\"\n    }\n\n    private static func paceDurationText(seconds: TimeInterval, now: Date) -> String {\n        let date = now.addingTimeInterval(seconds)\n        let countdown = UsageFormatter.resetCountdownDescription(from: date, now: now)\n        if countdown == \"now\" { return \"now\" }\n        if countdown.hasPrefix(\"in \") { return String(countdown.dropFirst(3)) }\n        return countdown\n    }\n\n    private static func colorizeUsage(_ text: String, remainingPercent: Double, useColor: Bool) -> String {\n        guard useColor else { return text }\n\n        let code = switch remainingPercent {\n        case ..<10:\n            \"31\" // red\n        case ..<25:\n            \"33\" // yellow\n        default:\n            \"32\" // green\n        }\n        return self.ansi(code, text)\n    }\n\n    private static func colorize(\n        _ text: String,\n        indicator: ProviderStatusPayload.ProviderStatusIndicator,\n        useColor: Bool)\n        -> String\n    {\n        guard useColor else { return text }\n        let code = switch indicator {\n        case .none: \"32\" // green\n        case .minor: \"33\" // yellow\n        case .major, .critical: \"31\" // red\n        case .maintenance: \"34\" // blue\n        case .unknown: \"90\" // gray\n        }\n        return self.ansi(code, text)\n    }\n\n    private static func ansi(_ code: String, _ text: String) -> String {\n        \"\\u{001B}[\\(code)m\\(text)\\u{001B}[0m\"\n    }\n}\n\nstruct RenderContext {\n    let header: String\n    let status: ProviderStatusPayload?\n    let useColor: Bool\n    let resetStyle: ResetTimeDisplayStyle\n    let notes: [String]\n\n    init(\n        header: String,\n        status: ProviderStatusPayload?,\n        useColor: Bool,\n        resetStyle: ResetTimeDisplayStyle,\n        notes: [String] = [])\n    {\n        self.header = header\n        self.status = status\n        self.useColor = useColor\n        self.resetStyle = resetStyle\n        self.notes = notes\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCLI/CLIUsageCommand.swift",
    "content": "import CodexBarCore\nimport Commander\nimport Foundation\n\nstruct UsageCommandContext {\n    let format: OutputFormat\n    let includeCredits: Bool\n    let sourceModeOverride: ProviderSourceMode?\n    let antigravityPlanDebug: Bool\n    let augmentDebug: Bool\n    let webDebugDumpHTML: Bool\n    let webTimeout: TimeInterval\n    let verbose: Bool\n    let useColor: Bool\n    let resetStyle: ResetTimeDisplayStyle\n    let jsonOnly: Bool\n    let fetcher: UsageFetcher\n    let claudeFetcher: ClaudeUsageFetcher\n    let browserDetection: BrowserDetection\n}\n\nstruct UsageCommandOutput {\n    var sections: [String] = []\n    var payload: [ProviderPayload] = []\n    var exitCode: ExitCode = .success\n}\n\nextension UsageCommandOutput {\n    mutating func merge(_ other: UsageCommandOutput) {\n        self.sections.append(contentsOf: other.sections)\n        self.payload.append(contentsOf: other.payload)\n        if other.exitCode != .success {\n            self.exitCode = other.exitCode\n        }\n    }\n}\n\nextension CodexBarCLI {\n    static func runUsage(_ values: ParsedValues) async {\n        let output = CLIOutputPreferences.from(values: values)\n        let config = Self.loadConfig(output: output)\n        let provider = Self.decodeProvider(from: values, config: config)\n        let format = output.format\n        let includeCredits = format == .json ? true : !values.flags.contains(\"noCredits\")\n        let includeStatus = values.flags.contains(\"status\")\n        let sourceModeRaw = values.options[\"source\"]?.last\n        let parsedSourceMode = Self.decodeSourceMode(from: values)\n        if sourceModeRaw != nil, parsedSourceMode == nil {\n            Self.exit(\n                code: .failure,\n                message: \"Error: --source must be auto|web|cli|oauth|api.\",\n                output: output,\n                kind: .args)\n        }\n        let antigravityPlanDebug = values.flags.contains(\"antigravityPlanDebug\")\n        let augmentDebug = values.flags.contains(\"augmentDebug\")\n        let webDebugDumpHTML = values.flags.contains(\"webDebugDumpHtml\")\n        let webTimeout = Self.decodeWebTimeout(from: values) ?? 60\n        let verbose = values.flags.contains(\"verbose\")\n        let noColor = values.flags.contains(\"noColor\")\n        let useColor = Self.shouldUseColor(noColor: noColor, format: format)\n        let resetStyle = Self.resetTimeDisplayStyleFromDefaults()\n        let providerList = provider.asList\n\n        let tokenSelection: TokenAccountCLISelection\n        do {\n            tokenSelection = try Self.decodeTokenAccountSelection(from: values)\n        } catch {\n            Self.exit(code: .failure, message: \"Error: \\(error.localizedDescription)\", output: output, kind: .args)\n        }\n\n        if tokenSelection.allAccounts, tokenSelection.label != nil || tokenSelection.index != nil {\n            Self.exit(\n                code: .failure,\n                message: \"Error: --all-accounts cannot be combined with --account or --account-index.\",\n                output: output,\n                kind: .args)\n        }\n\n        if tokenSelection.usesOverride {\n            guard providerList.count == 1 else {\n                Self.exit(\n                    code: .failure,\n                    message: \"Error: account selection requires a single provider.\",\n                    output: output,\n                    kind: .args)\n            }\n            guard TokenAccountSupportCatalog.support(for: providerList[0]) != nil else {\n                Self.exit(\n                    code: .failure,\n                    message: \"Error: \\(providerList[0].rawValue) does not support token accounts.\",\n                    output: output,\n                    kind: .args)\n            }\n        }\n\n        #if !os(macOS)\n        if let parsedSourceMode {\n            let requiresWeb = providerList.contains { selectedProvider in\n                Self.sourceModeRequiresWebSupport(parsedSourceMode, provider: selectedProvider)\n            }\n            if requiresWeb {\n                Self.exit(\n                    code: .failure,\n                    message: \"Error: selected source requires web support and is only supported on macOS.\",\n                    output: output,\n                    kind: .runtime)\n            }\n        }\n        #endif\n\n        let browserDetection = BrowserDetection()\n        let fetcher = UsageFetcher()\n        let claudeFetcher = ClaudeUsageFetcher(browserDetection: browserDetection)\n        let tokenContext: TokenAccountCLIContext\n        do {\n            tokenContext = try TokenAccountCLIContext(\n                selection: tokenSelection,\n                config: config,\n                verbose: verbose)\n        } catch {\n            Self.exit(code: .failure, message: \"Error: \\(error.localizedDescription)\", output: output, kind: .config)\n        }\n\n        var sections: [String] = []\n        var payload: [ProviderPayload] = []\n        var exitCode: ExitCode = .success\n        let command = UsageCommandContext(\n            format: format,\n            includeCredits: includeCredits,\n            sourceModeOverride: parsedSourceMode,\n            antigravityPlanDebug: antigravityPlanDebug,\n            augmentDebug: augmentDebug,\n            webDebugDumpHTML: webDebugDumpHTML,\n            webTimeout: webTimeout,\n            verbose: verbose,\n            useColor: useColor,\n            resetStyle: resetStyle,\n            jsonOnly: output.jsonOnly,\n            fetcher: fetcher,\n            claudeFetcher: claudeFetcher,\n            browserDetection: browserDetection)\n\n        for p in providerList {\n            let status = includeStatus ? await Self.fetchStatus(for: p) : nil\n            // CLI usage should not clear Keychain cooldowns or attempt interactive Keychain prompts.\n            let output = await ProviderInteractionContext.$current.withValue(.background) {\n                await Self.fetchUsageOutputs(\n                    provider: p,\n                    status: status,\n                    tokenContext: tokenContext,\n                    command: command)\n            }\n            if output.exitCode != .success {\n                exitCode = output.exitCode\n            }\n            sections.append(contentsOf: output.sections)\n            payload.append(contentsOf: output.payload)\n        }\n\n        switch format {\n        case .text:\n            if !sections.isEmpty {\n                print(sections.joined(separator: \"\\n\\n\"))\n            }\n        case .json:\n            if !payload.isEmpty {\n                Self.printJSON(payload, pretty: output.pretty)\n            }\n        }\n\n        Self.exit(code: exitCode, output: output, kind: exitCode == .success ? .runtime : .provider)\n    }\n\n    static func fetchUsageOutputs(\n        provider: UsageProvider,\n        status: ProviderStatusPayload?,\n        tokenContext: TokenAccountCLIContext,\n        command: UsageCommandContext) async -> UsageCommandOutput\n    {\n        let accounts: [ProviderTokenAccount]\n        do {\n            accounts = try tokenContext.resolvedAccounts(for: provider)\n        } catch {\n            return Self.usageOutputForAccountResolutionError(\n                provider: provider,\n                status: status,\n                command: command,\n                error: error)\n        }\n\n        let selections = Self.accountSelections(from: accounts)\n        var output = UsageCommandOutput()\n        for account in selections {\n            let result = await Self.fetchUsageOutput(\n                provider: provider,\n                account: account,\n                status: status,\n                tokenContext: tokenContext,\n                command: command)\n            output.merge(result)\n        }\n        return output\n    }\n\n    private static func accountSelections(from accounts: [ProviderTokenAccount]) -> [ProviderTokenAccount?] {\n        if accounts.isEmpty { return [nil] }\n        return accounts.map { Optional($0) }\n    }\n\n    private static func usageOutputForAccountResolutionError(\n        provider: UsageProvider,\n        status: ProviderStatusPayload?,\n        command: UsageCommandContext,\n        error: Error) -> UsageCommandOutput\n    {\n        var output = UsageCommandOutput()\n        output.exitCode = .failure\n        if command.format == .json {\n            output.payload.append(Self.makeProviderErrorPayload(\n                provider: provider,\n                account: nil,\n                source: command.sourceModeOverride?.rawValue ?? \"auto\",\n                status: status,\n                error: error,\n                kind: .provider))\n        } else if !command.jsonOnly {\n            Self.writeStderr(\"Error: \\(error.localizedDescription)\\n\")\n        }\n        return output\n    }\n\n    private static func fetchUsageOutput(\n        provider: UsageProvider,\n        account: ProviderTokenAccount?,\n        status: ProviderStatusPayload?,\n        tokenContext: TokenAccountCLIContext,\n        command: UsageCommandContext) async -> UsageCommandOutput\n    {\n        var output = UsageCommandOutput()\n        let env = tokenContext.environment(\n            base: ProcessInfo.processInfo.environment,\n            provider: provider,\n            account: account)\n        let settings = tokenContext.settingsSnapshot(for: provider, account: account)\n        let configSource = tokenContext.preferredSourceMode(for: provider)\n        let baseSource = command.sourceModeOverride ?? configSource\n        let effectiveSourceMode = tokenContext.effectiveSourceMode(\n            base: baseSource,\n            provider: provider,\n            account: account)\n\n        #if !os(macOS)\n        if Self.sourceModeRequiresWebSupport(effectiveSourceMode, provider: provider) {\n            return Self.webSourceUnsupportedOutput(\n                provider: provider,\n                account: account,\n                source: effectiveSourceMode.rawValue,\n                status: status,\n                command: command)\n        }\n        #endif\n\n        let fetchContext = ProviderFetchContext(\n            runtime: .cli,\n            sourceMode: effectiveSourceMode,\n            includeCredits: command.includeCredits,\n            webTimeout: command.webTimeout,\n            webDebugDumpHTML: command.webDebugDumpHTML,\n            verbose: command.verbose,\n            env: env,\n            settings: settings,\n            fetcher: command.fetcher,\n            claudeFetcher: command.claudeFetcher,\n            browserDetection: command.browserDetection)\n        let outcome = await Self.fetchProviderUsage(\n            provider: provider,\n            context: fetchContext)\n        if command.verbose, !command.jsonOnly {\n            Self.printFetchAttempts(provider: provider, attempts: outcome.attempts)\n        }\n\n        switch outcome.result {\n        case let .success(result):\n            let antigravityPlanInfo = await Self.fetchAntigravityPlanInfoIfNeeded(\n                provider: provider,\n                command: command)\n            await Self.emitAugmentDebugIfNeeded(provider: provider, command: command)\n\n            var usage = result.usage.scoped(to: provider)\n            if let account {\n                usage = tokenContext.applyAccountLabel(usage, provider: provider, account: account)\n            }\n\n            var dashboard = result.dashboard\n            if dashboard == nil, command.format == .json, provider == .codex {\n                dashboard = Self.loadOpenAIDashboardIfAvailable(usage: usage, fetcher: command.fetcher)\n            }\n\n            let descriptor = ProviderDescriptorRegistry.descriptor(for: provider)\n            let shouldDetectVersion = descriptor.cli.versionDetector != nil\n                && result.strategyKind != ProviderFetchKind.webDashboard\n            let version = Self.normalizeVersion(\n                raw: shouldDetectVersion\n                    ? Self.detectVersion(for: provider, browserDetection: command.browserDetection)\n                    : nil)\n            let source = result.sourceLabel\n            let header = Self.makeHeader(provider: provider, version: version, source: source)\n            let notes = Self.usageTextNotes(\n                provider: provider,\n                sourceMode: effectiveSourceMode,\n                resolvedSourceLabel: source)\n\n            switch command.format {\n            case .text:\n                var text = CLIRenderer.renderText(\n                    provider: provider,\n                    snapshot: usage,\n                    credits: result.credits,\n                    context: RenderContext(\n                        header: header,\n                        status: status,\n                        useColor: command.useColor,\n                        resetStyle: command.resetStyle,\n                        notes: notes))\n                if let dashboard, provider == .codex, effectiveSourceMode.usesWeb {\n                    text += \"\\n\" + Self.renderOpenAIWebDashboardText(dashboard)\n                }\n                output.sections.append(text)\n            case .json:\n                output.payload.append(ProviderPayload(\n                    provider: provider,\n                    account: account?.label,\n                    version: version,\n                    source: source,\n                    status: status,\n                    usage: usage,\n                    credits: result.credits,\n                    antigravityPlanInfo: antigravityPlanInfo,\n                    openaiDashboard: dashboard,\n                    error: nil))\n            }\n        case let .failure(error):\n            output.exitCode = Self.mapError(error)\n            if command.format == .json {\n                output.payload.append(Self.makeProviderErrorPayload(\n                    provider: provider,\n                    account: account?.label,\n                    source: effectiveSourceMode.rawValue,\n                    status: status,\n                    error: error,\n                    kind: .provider))\n            } else if !command.jsonOnly {\n                if let account {\n                    Self.writeStderr(\n                        \"Error (\\(provider.rawValue) - \\(account.label)): \\(error.localizedDescription)\\n\")\n                } else {\n                    Self.writeStderr(\"Error: \\(error.localizedDescription)\\n\")\n                }\n                if let summary = Self.kiloAutoFallbackSummary(\n                    provider: provider,\n                    sourceMode: effectiveSourceMode,\n                    attempts: outcome.attempts)\n                {\n                    Self.writeStderr(\"\\(summary)\\n\")\n                }\n            }\n        }\n\n        return output\n    }\n\n    private static func fetchAntigravityPlanInfoIfNeeded(\n        provider: UsageProvider,\n        command: UsageCommandContext) async -> AntigravityPlanInfoSummary?\n    {\n        guard command.antigravityPlanDebug,\n              provider == .antigravity,\n              !command.jsonOnly\n        else {\n            return nil\n        }\n        let info = try? await AntigravityStatusProbe().fetchPlanInfoSummary()\n        if command.format == .text, let info {\n            Self.printAntigravityPlanInfo(info)\n        }\n        return info\n    }\n\n    private static func emitAugmentDebugIfNeeded(\n        provider: UsageProvider,\n        command: UsageCommandContext) async\n    {\n        guard command.augmentDebug, provider == .augment else { return }\n        #if os(macOS)\n        let dump = await AugmentStatusProbe.latestDumps()\n        if command.format == .text, !dump.isEmpty, !command.jsonOnly {\n            Self.writeStderr(\"Augment API responses:\\n\\(dump)\\n\")\n        }\n        #endif\n    }\n\n    private static func webSourceUnsupportedOutput(\n        provider: UsageProvider,\n        account: ProviderTokenAccount?,\n        source: String,\n        status: ProviderStatusPayload?,\n        command: UsageCommandContext) -> UsageCommandOutput\n    {\n        var output = UsageCommandOutput()\n        let error = NSError(\n            domain: \"CodexBarCLI\",\n            code: 1,\n            userInfo: [NSLocalizedDescriptionKey:\n                \"Error: selected source requires web support and is only supported on macOS.\"])\n        output.exitCode = .failure\n        if command.format == .json {\n            output.payload.append(Self.makeProviderErrorPayload(\n                provider: provider,\n                account: account?.label,\n                source: source,\n                status: status,\n                error: error,\n                kind: .runtime))\n        } else if !command.jsonOnly {\n            Self.writeStderr(\"Error: \\(error.localizedDescription)\\n\")\n        }\n        return output\n    }\n\n    static func sourceModeRequiresWebSupport(_ sourceMode: ProviderSourceMode, provider: UsageProvider) -> Bool {\n        switch sourceMode {\n        case .web:\n            true\n        case .auto:\n            ProviderDescriptorRegistry.descriptor(for: provider).fetchPlan.sourceModes.contains(.web)\n        case .cli, .oauth, .api:\n            false\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCLI/TokenAccountCLI.swift",
    "content": "import CodexBarCore\nimport Commander\nimport Foundation\n\nstruct TokenAccountCLISelection {\n    let label: String?\n    let index: Int?\n    let allAccounts: Bool\n\n    var usesOverride: Bool {\n        self.label != nil || self.index != nil || self.allAccounts\n    }\n}\n\nenum TokenAccountCLIError: LocalizedError {\n    case noAccounts(UsageProvider)\n    case accountNotFound(UsageProvider, String)\n    case indexOutOfRange(UsageProvider, Int, Int)\n\n    var errorDescription: String? {\n        switch self {\n        case let .noAccounts(provider):\n            \"No token accounts configured for \\(provider.rawValue).\"\n        case let .accountNotFound(provider, label):\n            \"No token account labeled '\\(label)' for \\(provider.rawValue).\"\n        case let .indexOutOfRange(provider, index, count):\n            \"Token account index \\(index) out of range for \\(provider.rawValue) (1-\\(count)).\"\n        }\n    }\n}\n\nstruct TokenAccountCLIContext {\n    let selection: TokenAccountCLISelection\n    let config: CodexBarConfig\n    let accountsByProvider: [UsageProvider: ProviderTokenAccountData]\n\n    init(selection: TokenAccountCLISelection, config: CodexBarConfig, verbose _: Bool) throws {\n        self.selection = selection\n        self.config = config\n        self.accountsByProvider = Dictionary(uniqueKeysWithValues: config.providers.compactMap { provider in\n            guard let accounts = provider.tokenAccounts else { return nil }\n            return (provider.id, accounts)\n        })\n    }\n\n    func resolvedAccounts(for provider: UsageProvider) throws -> [ProviderTokenAccount] {\n        guard TokenAccountSupportCatalog.support(for: provider) != nil else { return [] }\n        guard let data = self.accountsByProvider[provider], !data.accounts.isEmpty else {\n            if self.selection.usesOverride {\n                throw TokenAccountCLIError.noAccounts(provider)\n            }\n            return []\n        }\n\n        if self.selection.allAccounts {\n            return data.accounts\n        }\n\n        if let label = self.selection.label?.trimmingCharacters(in: .whitespacesAndNewlines), !label.isEmpty {\n            let normalized = label.lowercased()\n            if let match = data.accounts.first(where: { $0.label.lowercased() == normalized }) {\n                return [match]\n            }\n            throw TokenAccountCLIError.accountNotFound(provider, label)\n        }\n\n        if let index = self.selection.index {\n            guard index >= 0, index < data.accounts.count else {\n                throw TokenAccountCLIError.indexOutOfRange(provider, index + 1, data.accounts.count)\n            }\n            return [data.accounts[index]]\n        }\n\n        let clamped = data.clampedActiveIndex()\n        return [data.accounts[clamped]]\n    }\n\n    func settingsSnapshot(for provider: UsageProvider, account: ProviderTokenAccount?) -> ProviderSettingsSnapshot? {\n        let config = self.providerConfig(for: provider)\n\n        switch provider {\n        case .codex:\n            let cookieHeader = self.manualCookieHeader(provider: provider, account: account, config: config)\n            let cookieSource = self.cookieSource(provider: provider, account: account, config: config)\n            return self.makeSnapshot(\n                codex: ProviderSettingsSnapshot.CodexProviderSettings(\n                    usageDataSource: .auto,\n                    cookieSource: cookieSource,\n                    manualCookieHeader: cookieHeader))\n        case .claude:\n            let routing = self.claudeCredentialRouting(account: account, config: config)\n            let claudeSource: ClaudeUsageDataSource = routing.isOAuth ? .oauth : .auto\n            let cookieSource = routing.isOAuth\n                ? ProviderCookieSource.off\n                : self.cookieSource(provider: provider, account: account, config: config)\n            return self.makeSnapshot(\n                claude: ProviderSettingsSnapshot.ClaudeProviderSettings(\n                    usageDataSource: claudeSource,\n                    webExtrasEnabled: false,\n                    cookieSource: cookieSource,\n                    manualCookieHeader: routing.manualCookieHeader))\n        case .cursor:\n            let cookieHeader = self.manualCookieHeader(provider: provider, account: account, config: config)\n            let cookieSource = self.cookieSource(provider: provider, account: account, config: config)\n            return self.makeSnapshot(\n                cursor: ProviderSettingsSnapshot.CursorProviderSettings(\n                    cookieSource: cookieSource,\n                    manualCookieHeader: cookieHeader))\n        case .opencode:\n            let cookieHeader = self.manualCookieHeader(provider: provider, account: account, config: config)\n            let cookieSource = self.cookieSource(provider: provider, account: account, config: config)\n            return self.makeSnapshot(\n                opencode: ProviderSettingsSnapshot.OpenCodeProviderSettings(\n                    cookieSource: cookieSource,\n                    manualCookieHeader: cookieHeader,\n                    workspaceID: config?.workspaceID))\n        case .alibaba:\n            let cookieHeader = self.manualCookieHeader(provider: provider, account: account, config: config)\n            let cookieSource = self.cookieSource(provider: provider, account: account, config: config)\n            return self.makeSnapshot(\n                alibaba: ProviderSettingsSnapshot.AlibabaCodingPlanProviderSettings(\n                    cookieSource: cookieSource,\n                    manualCookieHeader: cookieHeader,\n                    apiRegion: self.resolveAlibabaCodingPlanRegion(config)))\n        case .factory:\n            let cookieHeader = self.manualCookieHeader(provider: provider, account: account, config: config)\n            let cookieSource = self.cookieSource(provider: provider, account: account, config: config)\n            return self.makeSnapshot(\n                factory: ProviderSettingsSnapshot.FactoryProviderSettings(\n                    cookieSource: cookieSource,\n                    manualCookieHeader: cookieHeader))\n        case .minimax:\n            let cookieHeader = self.manualCookieHeader(provider: provider, account: account, config: config)\n            let cookieSource = self.cookieSource(provider: provider, account: account, config: config)\n            return self.makeSnapshot(\n                minimax: ProviderSettingsSnapshot.MiniMaxProviderSettings(\n                    cookieSource: cookieSource,\n                    manualCookieHeader: cookieHeader,\n                    apiRegion: self.resolveMiniMaxRegion(config)))\n        case .augment:\n            let cookieHeader = self.manualCookieHeader(provider: provider, account: account, config: config)\n            let cookieSource = self.cookieSource(provider: provider, account: account, config: config)\n            return self.makeSnapshot(\n                augment: ProviderSettingsSnapshot.AugmentProviderSettings(\n                    cookieSource: cookieSource,\n                    manualCookieHeader: cookieHeader))\n        case .amp:\n            let cookieHeader = self.manualCookieHeader(provider: provider, account: account, config: config)\n            let cookieSource = self.cookieSource(provider: provider, account: account, config: config)\n            return self.makeSnapshot(\n                amp: ProviderSettingsSnapshot.AmpProviderSettings(\n                    cookieSource: cookieSource,\n                    manualCookieHeader: cookieHeader))\n        case .ollama:\n            let cookieHeader = self.manualCookieHeader(provider: provider, account: account, config: config)\n            let cookieSource = self.cookieSource(provider: provider, account: account, config: config)\n            return self.makeSnapshot(\n                ollama: ProviderSettingsSnapshot.OllamaProviderSettings(\n                    cookieSource: cookieSource,\n                    manualCookieHeader: cookieHeader))\n        case .kimi:\n            let cookieHeader = self.manualCookieHeader(provider: provider, account: account, config: config)\n            let cookieSource = self.cookieSource(provider: provider, account: account, config: config)\n            return self.makeSnapshot(\n                kimi: ProviderSettingsSnapshot.KimiProviderSettings(\n                    cookieSource: cookieSource,\n                    manualCookieHeader: cookieHeader))\n        case .zai:\n            return self.makeSnapshot(\n                zai: ProviderSettingsSnapshot.ZaiProviderSettings(apiRegion: self.resolveZaiRegion(config)))\n        case .kilo:\n            return self.makeSnapshot(\n                kilo: ProviderSettingsSnapshot.KiloProviderSettings(\n                    usageDataSource: Self.kiloUsageDataSource(from: config?.source),\n                    extrasEnabled: Self.kiloExtrasEnabled(from: config)))\n        case .jetbrains:\n            return self.makeSnapshot(\n                jetbrains: ProviderSettingsSnapshot.JetBrainsProviderSettings(\n                    ideBasePath: nil))\n        case .gemini, .antigravity, .copilot, .kiro, .vertexai, .kimik2, .synthetic, .openrouter, .warp:\n            return nil\n        }\n    }\n\n    private func makeSnapshot(\n        codex: ProviderSettingsSnapshot.CodexProviderSettings? = nil,\n        claude: ProviderSettingsSnapshot.ClaudeProviderSettings? = nil,\n        cursor: ProviderSettingsSnapshot.CursorProviderSettings? = nil,\n        opencode: ProviderSettingsSnapshot.OpenCodeProviderSettings? = nil,\n        alibaba: ProviderSettingsSnapshot.AlibabaCodingPlanProviderSettings? = nil,\n        factory: ProviderSettingsSnapshot.FactoryProviderSettings? = nil,\n        minimax: ProviderSettingsSnapshot.MiniMaxProviderSettings? = nil,\n        zai: ProviderSettingsSnapshot.ZaiProviderSettings? = nil,\n        kilo: ProviderSettingsSnapshot.KiloProviderSettings? = nil,\n        kimi: ProviderSettingsSnapshot.KimiProviderSettings? = nil,\n        augment: ProviderSettingsSnapshot.AugmentProviderSettings? = nil,\n        amp: ProviderSettingsSnapshot.AmpProviderSettings? = nil,\n        ollama: ProviderSettingsSnapshot.OllamaProviderSettings? = nil,\n        jetbrains: ProviderSettingsSnapshot.JetBrainsProviderSettings? = nil) -> ProviderSettingsSnapshot\n    {\n        ProviderSettingsSnapshot.make(\n            codex: codex,\n            claude: claude,\n            cursor: cursor,\n            opencode: opencode,\n            alibaba: alibaba,\n            factory: factory,\n            minimax: minimax,\n            zai: zai,\n            kilo: kilo,\n            kimi: kimi,\n            augment: augment,\n            amp: amp,\n            ollama: ollama,\n            jetbrains: jetbrains)\n    }\n\n    func environment(\n        base: [String: String],\n        provider: UsageProvider,\n        account: ProviderTokenAccount?) -> [String: String]\n    {\n        let providerConfig = self.providerConfig(for: provider)\n        var env = ProviderConfigEnvironment.applyAPIKeyOverride(\n            base: base,\n            provider: provider,\n            config: providerConfig)\n        // If token account is selected, use its token instead of config's apiKey\n        if let account,\n           let override = TokenAccountSupportCatalog.envOverride(for: provider, token: account.token)\n        {\n            for (key, value) in override {\n                env[key] = value\n            }\n        }\n        return env\n    }\n\n    func applyAccountLabel(\n        _ snapshot: UsageSnapshot,\n        provider: UsageProvider,\n        account: ProviderTokenAccount) -> UsageSnapshot\n    {\n        let label = account.label.trimmingCharacters(in: .whitespacesAndNewlines)\n        guard !label.isEmpty else { return snapshot }\n        let existing = snapshot.identity(for: provider)\n        let email = existing?.accountEmail?.trimmingCharacters(in: .whitespacesAndNewlines)\n        let resolvedEmail = (email?.isEmpty ?? true) ? label : email\n        let identity = ProviderIdentitySnapshot(\n            providerID: provider,\n            accountEmail: resolvedEmail,\n            accountOrganization: existing?.accountOrganization,\n            loginMethod: existing?.loginMethod)\n        return snapshot.withIdentity(identity)\n    }\n\n    func effectiveSourceMode(\n        base: ProviderSourceMode,\n        provider: UsageProvider,\n        account: ProviderTokenAccount?) -> ProviderSourceMode\n    {\n        guard base == .auto,\n              provider == .claude\n        else {\n            return base\n        }\n        let config = self.providerConfig(for: provider)\n        return self.claudeCredentialRouting(account: account, config: config).isOAuth ? .oauth : base\n    }\n\n    func preferredSourceMode(for provider: UsageProvider) -> ProviderSourceMode {\n        let config = self.providerConfig(for: provider)\n        return config?.source ?? .auto\n    }\n\n    private func providerConfig(for provider: UsageProvider) -> ProviderConfig? {\n        self.config.providerConfig(for: provider)\n    }\n\n    private func manualCookieHeader(\n        provider: UsageProvider,\n        account: ProviderTokenAccount?,\n        config: ProviderConfig?) -> String?\n    {\n        if let account,\n           let support = TokenAccountSupportCatalog.support(for: provider),\n           case .cookieHeader = support.injection\n        {\n            let header = TokenAccountSupportCatalog.normalizedCookieHeader(account.token, support: support)\n            return header.isEmpty ? nil : header\n        }\n        return config?.sanitizedCookieHeader\n    }\n\n    private func cookieSource(\n        provider: UsageProvider,\n        account: ProviderTokenAccount?,\n        config: ProviderConfig?) -> ProviderCookieSource\n    {\n        if account != nil, TokenAccountSupportCatalog.support(for: provider)?.requiresManualCookieSource == true {\n            return .manual\n        }\n        if let override = config?.cookieSource { return override }\n        if config?.sanitizedCookieHeader != nil {\n            return .manual\n        }\n        return .auto\n    }\n\n    private func resolveZaiRegion(_ config: ProviderConfig?) -> ZaiAPIRegion {\n        guard let raw = config?.region?.trimmingCharacters(in: .whitespacesAndNewlines),\n              !raw.isEmpty\n        else {\n            return .global\n        }\n        return ZaiAPIRegion(rawValue: raw) ?? .global\n    }\n\n    private func resolveMiniMaxRegion(_ config: ProviderConfig?) -> MiniMaxAPIRegion {\n        guard let raw = config?.region?.trimmingCharacters(in: .whitespacesAndNewlines),\n              !raw.isEmpty\n        else {\n            return .global\n        }\n        return MiniMaxAPIRegion(rawValue: raw) ?? .global\n    }\n\n    private func resolveAlibabaCodingPlanRegion(_ config: ProviderConfig?) -> AlibabaCodingPlanAPIRegion {\n        guard let raw = config?.region?.trimmingCharacters(in: .whitespacesAndNewlines),\n              !raw.isEmpty\n        else {\n            return .international\n        }\n        return AlibabaCodingPlanAPIRegion(rawValue: raw) ?? .international\n    }\n\n    private static func kiloUsageDataSource(from source: ProviderSourceMode?) -> KiloUsageDataSource {\n        guard let source else { return .auto }\n        switch source {\n        case .auto, .web, .oauth:\n            return .auto\n        case .api:\n            return .api\n        case .cli:\n            return .cli\n        }\n    }\n\n    private static func kiloExtrasEnabled(from config: ProviderConfig?) -> Bool {\n        guard self.kiloUsageDataSource(from: config?.source) == .auto else { return false }\n        return config?.extrasEnabled ?? false\n    }\n\n    private func claudeCredentialRouting(\n        account: ProviderTokenAccount?,\n        config: ProviderConfig?) -> ClaudeCredentialRouting\n    {\n        let manualCookieHeader = account == nil ? config?.sanitizedCookieHeader : nil\n        return ClaudeCredentialRouting.resolve(\n            tokenAccountToken: account?.token,\n            manualCookieHeader: manualCookieHeader)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarClaudeWatchdog/main.swift",
    "content": "import Darwin\nimport Foundation\n\nprivate enum WatchdogExitCode {\n    static let usage: Int32 = 64\n    static let spawnFailed: Int32 = 70\n}\n\nprivate nonisolated(unsafe) var globalChildPID: pid_t = 0\nprivate nonisolated(unsafe) var globalShouldTerminate: Int32 = 0\n\nprivate func usageAndExit() -> Never {\n    fputs(\"Usage: CodexBarClaudeWatchdog -- <binary> [args...]\\n\", stderr)\n    Darwin.exit(WatchdogExitCode.usage)\n}\n\nprivate func killProcessTree(childPID: pid_t, graceSeconds: TimeInterval = 0.5) {\n    let pgid = getpgid(childPID)\n    if pgid > 0 {\n        kill(-pgid, SIGTERM)\n    } else {\n        kill(childPID, SIGTERM)\n    }\n\n    let deadline = Date().addingTimeInterval(graceSeconds)\n    var status: Int32 = 0\n    while Date() < deadline {\n        let rc = waitpid(childPID, &status, WNOHANG)\n        if rc == childPID { return }\n        usleep(50000)\n    }\n\n    if pgid > 0 {\n        kill(-pgid, SIGKILL)\n    } else {\n        kill(childPID, SIGKILL)\n    }\n}\n\nprivate func exitCode(fromWaitStatus status: Int32) -> Int32 {\n    // Swift can't import wait(2) macros (function-like macros). Use the classic encoding:\n    // - low 7 bits: signal number (0 means exited)\n    // - high byte: exit status (when exited)\n    let low = status & 0x7F\n    if low == 0 {\n        return (status >> 8) & 0xFF\n    }\n    if low != 0x7F {\n        return 128 + low\n    }\n    return 1\n}\n\nlet argv = CommandLine.arguments\nguard let sep = argv.firstIndex(of: \"--\") else { usageAndExit() }\nlet childArgs = Array(argv[(sep + 1)...])\nguard !childArgs.isEmpty else { usageAndExit() }\n\nlet childBinary = childArgs[0]\nlet childArgv = childArgs\n\nlet spawnResult: Int32 = childArgv.withUnsafeBufferPointer { buffer in\n    var cStrings: [UnsafeMutablePointer<CChar>?] = buffer\n        .map { strdup($0) }\n    cStrings.append(nil)\n    defer { cStrings.forEach { if let p = $0 { free(p) } } }\n\n    return cStrings.withUnsafeMutableBufferPointer { cBuffer in\n        var pid: pid_t = 0\n        let rc: Int32 = childBinary.withCString { childPath in\n            posix_spawnp(&pid, childPath, nil, nil, cBuffer.baseAddress, environ)\n        }\n        if rc == 0, pid > 0 {\n            globalChildPID = pid\n        }\n        return rc\n    }\n}\n\nguard spawnResult == 0, globalChildPID > 0 else {\n    fputs(\"CodexBarClaudeWatchdog: failed to spawn child: \\(childBinary) (rc=\\(spawnResult))\\n\", stderr)\n    Darwin.exit(WatchdogExitCode.spawnFailed)\n}\n\n_ = setpgid(globalChildPID, globalChildPID)\n\nprivate func terminateChild() {\n    if globalChildPID > 0 {\n        killProcessTree(childPID: globalChildPID)\n    }\n}\n\nprivate func handleTerminationSignal(_ sig: Int32) {\n    globalShouldTerminate = sig\n}\n\nsignal(SIGTERM, handleTerminationSignal)\nsignal(SIGINT, handleTerminationSignal)\nsignal(SIGHUP, handleTerminationSignal)\n\nvar status: Int32 = 0\nwhile true {\n    let rc = waitpid(globalChildPID, &status, WNOHANG)\n    if rc == globalChildPID {\n        Darwin.exit(exitCode(fromWaitStatus: status))\n    }\n\n    if globalShouldTerminate != 0 {\n        let sig = globalShouldTerminate\n        terminateChild()\n        _ = waitpid(globalChildPID, &status, 0)\n        Darwin.exit(128 + sig)\n    }\n\n    if getppid() == 1 {\n        terminateChild()\n        _ = waitpid(globalChildPID, &status, 0)\n        Darwin.exit(exitCode(fromWaitStatus: status))\n    }\n\n    usleep(200_000)\n}\n"
  },
  {
    "path": "Sources/CodexBarClaudeWebProbe/main.swift",
    "content": "import CodexBarCore\nimport Foundation\n\n@main\nenum CodexBarClaudeWebProbe {\n    private static let defaultEndpoints: [String] = [\n        \"https://claude.ai/api/organizations\",\n        \"https://claude.ai/api/organizations/{orgId}/usage\",\n        \"https://claude.ai/api/organizations/{orgId}/overage_spend_limit\",\n        \"https://claude.ai/api/organizations/{orgId}/members\",\n        \"https://claude.ai/api/organizations/{orgId}/me\",\n        \"https://claude.ai/api/organizations/{orgId}/billing\",\n        \"https://claude.ai/api/me\",\n        \"https://claude.ai/api/user\",\n        \"https://claude.ai/api/session\",\n        \"https://claude.ai/api/account\",\n        \"https://claude.ai/settings/billing\",\n        \"https://claude.ai/settings/account\",\n        \"https://claude.ai/settings/usage\",\n    ]\n\n    static func main() async {\n        let args = CommandLine.arguments.dropFirst()\n        let endpoints = args.isEmpty ? Self.defaultEndpoints : Array(args)\n        let includePreview = ProcessInfo.processInfo.environment[\"CLAUDE_WEB_PROBE_PREVIEW\"] == \"1\"\n\n        do {\n            let results = try await ClaudeWebAPIFetcher.probeEndpoints(\n                endpoints,\n                browserDetection: BrowserDetection(cacheTTL: 0),\n                includePreview: includePreview)\n            for result in results {\n                Self.printResult(result)\n            }\n        } catch {\n            fputs(\"Probe failed: \\(error.localizedDescription)\\n\", stderr)\n            Darwin.exit(1)\n        }\n    }\n\n    private static func printResult(_ result: ClaudeWebAPIFetcher.ProbeResult) {\n        let status = result.statusCode.map(String.init) ?? \"error\"\n        print(\"==> \\(result.url)\")\n        print(\"status: \\(status)\")\n        if let contentType = result.contentType { print(\"content-type: \\(contentType)\") }\n        if !result.topLevelKeys.isEmpty {\n            print(\"keys: \\(result.topLevelKeys.joined(separator: \", \"))\")\n        }\n        if !result.emails.isEmpty {\n            print(\"emails: \\(result.emails.joined(separator: \", \"))\")\n        }\n        if !result.planHints.isEmpty {\n            print(\"plan-hints: \\(result.planHints.joined(separator: \", \"))\")\n        }\n        if !result.notableFields.isEmpty {\n            print(\"fields: \\(result.notableFields.joined(separator: \", \"))\")\n        }\n        if let preview = result.bodyPreview, !preview.isEmpty {\n            print(\"preview: \\(preview)\")\n        }\n        print(\"\")\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/BrowserCookieAccessGate.swift",
    "content": "import Foundation\n\n#if os(macOS)\nimport os.lock\nimport SweetCookieKit\n\npublic enum BrowserCookieAccessGate {\n    private struct State {\n        var loaded = false\n        var deniedUntilByBrowser: [String: Date] = [:]\n    }\n\n    private static let lock = OSAllocatedUnfairLock<State>(initialState: State())\n    private static let defaultsKey = \"browserCookieAccessDeniedUntil\"\n    private static let cooldownInterval: TimeInterval = 60 * 60 * 6\n    private static let log = CodexBarLog.logger(LogCategories.browserCookieGate)\n\n    public static func shouldAttempt(_ browser: Browser, now: Date = Date()) -> Bool {\n        guard browser.usesKeychainForCookieDecryption else { return true }\n        guard !KeychainAccessGate.isDisabled else { return false }\n        return self.lock.withLock { state in\n            self.loadIfNeeded(&state)\n            if let blockedUntil = state.deniedUntilByBrowser[browser.rawValue] {\n                if blockedUntil > now {\n                    self.log.debug(\n                        \"Cookie access blocked\",\n                        metadata: [\"browser\": browser.displayName, \"until\": \"\\(blockedUntil.timeIntervalSince1970)\"])\n                    return false\n                }\n                state.deniedUntilByBrowser.removeValue(forKey: browser.rawValue)\n                self.persist(state)\n            }\n            if self.chromiumKeychainRequiresInteraction() {\n                state.deniedUntilByBrowser[browser.rawValue] = now.addingTimeInterval(self.cooldownInterval)\n                self.persist(state)\n                self.log.info(\n                    \"Cookie access requires keychain interaction; suppressing\",\n                    metadata: [\"browser\": browser.displayName])\n                return false\n            }\n            self.log.debug(\"Cookie access allowed\", metadata: [\"browser\": browser.displayName])\n            return true\n        }\n    }\n\n    public static func recordIfNeeded(_ error: Error, now: Date = Date()) {\n        guard let error = error as? BrowserCookieError else { return }\n        guard case .accessDenied = error else { return }\n        self.recordDenied(for: error.browser, now: now)\n    }\n\n    public static func recordDenied(for browser: Browser, now: Date = Date()) {\n        guard browser.usesKeychainForCookieDecryption else { return }\n        let blockedUntil = now.addingTimeInterval(self.cooldownInterval)\n        self.lock.withLock { state in\n            self.loadIfNeeded(&state)\n            state.deniedUntilByBrowser[browser.rawValue] = blockedUntil\n            self.persist(state)\n        }\n        self.log\n            .info(\n                \"Browser cookie access denied; suppressing prompts\",\n                metadata: [\n                    \"browser\": browser.displayName,\n                    \"until\": \"\\(blockedUntil.timeIntervalSince1970)\",\n                ])\n    }\n\n    public static func resetForTesting() {\n        self.lock.withLock { state in\n            state.loaded = true\n            state.deniedUntilByBrowser.removeAll()\n            UserDefaults.standard.removeObject(forKey: self.defaultsKey)\n        }\n    }\n\n    private static func chromiumKeychainRequiresInteraction() -> Bool {\n        for label in self.safeStorageLabels {\n            switch KeychainAccessPreflight.checkGenericPassword(service: label.service, account: label.account) {\n            case .allowed:\n                return false\n            case .interactionRequired:\n                return true\n            case .notFound, .failure:\n                continue\n            }\n        }\n        return false\n    }\n\n    private static let safeStorageLabels: [(service: String, account: String)] = Browser.safeStorageLabels\n\n    private static func loadIfNeeded(_ state: inout State) {\n        guard !state.loaded else { return }\n        state.loaded = true\n        guard let raw = UserDefaults.standard.dictionary(forKey: self.defaultsKey) as? [String: Double] else {\n            return\n        }\n        state.deniedUntilByBrowser = raw.compactMapValues { Date(timeIntervalSince1970: $0) }\n    }\n\n    private static func persist(_ state: State) {\n        let raw = state.deniedUntilByBrowser.mapValues { $0.timeIntervalSince1970 }\n        UserDefaults.standard.set(raw, forKey: self.defaultsKey)\n    }\n}\n#else\npublic enum BrowserCookieAccessGate {\n    public static func shouldAttempt(_ browser: Browser, now: Date = Date()) -> Bool {\n        true\n    }\n\n    public static func recordIfNeeded(_ error: Error, now: Date = Date()) {}\n    public static func recordDenied(for browser: Browser, now: Date = Date()) {}\n    public static func resetForTesting() {}\n}\n#endif\n"
  },
  {
    "path": "Sources/CodexBarCore/BrowserCookieImportOrder.swift",
    "content": "#if os(macOS)\nimport SweetCookieKit\n\npublic typealias BrowserCookieImportOrder = [Browser]\n#else\npublic struct Browser: Sendable, Hashable {\n    public init() {}\n}\n\npublic typealias BrowserCookieImportOrder = [Browser]\n#endif\n\nextension [Browser] {\n    /// Filters a browser list to sources worth attempting for cookie imports.\n    ///\n    /// This is intentionally stricter than \"app installed\": it aims to avoid unnecessary Keychain prompts.\n    public func cookieImportCandidates(using detection: BrowserDetection) -> [Browser] {\n        let candidates = self.filter { browser in\n            if KeychainAccessGate.isDisabled, browser.usesKeychainForCookieDecryption {\n                return false\n            }\n            return detection.isCookieSourceAvailable(browser)\n        }\n        return candidates.filter { BrowserCookieAccessGate.shouldAttempt($0) }\n    }\n\n    /// Filters a browser list to sources with usable profile data on disk.\n    public func browsersWithProfileData(using detection: BrowserDetection) -> [Browser] {\n        self.filter { detection.hasUsableProfileData($0) }\n    }\n}\n\n#if os(macOS)\nextension Browser {\n    var usesKeychainForCookieDecryption: Bool {\n        switch self {\n        case .safari, .firefox, .zen:\n            return false\n        case .chrome, .chromeBeta, .chromeCanary,\n             .arc, .arcBeta, .arcCanary,\n             .chatgptAtlas,\n             .chromium,\n             .brave, .braveBeta, .braveNightly,\n             .edge, .edgeBeta, .edgeCanary,\n             .helium,\n             .vivaldi,\n             .dia:\n            return true\n        @unknown default:\n            return true\n        }\n    }\n}\n#else\nextension Browser {\n    var usesKeychainForCookieDecryption: Bool {\n        false\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/CodexBarCore/BrowserDetection.swift",
    "content": "import Foundation\n#if os(macOS)\nimport os.lock\nimport SweetCookieKit\n\n/// Browser presence + profile heuristics.\n///\n/// Primary goal: avoid triggering unnecessary Keychain prompts (e.g. Chromium “Safe Storage”) by skipping\n/// cookie imports from browsers that have no profile data on disk.\npublic final class BrowserDetection: Sendable {\n    public static let defaultCacheTTL: TimeInterval = 60 * 10\n\n    private let cache = OSAllocatedUnfairLock<[CacheKey: CachedResult]>(initialState: [:])\n    private let homeDirectory: String\n    private let cacheTTL: TimeInterval\n    private let now: @Sendable () -> Date\n    private let fileExists: @Sendable (String) -> Bool\n    private let directoryContents: @Sendable (String) -> [String]?\n\n    private struct CachedResult {\n        let value: Bool\n        let timestamp: Date\n    }\n\n    private enum ProbeKind: Int, Hashable {\n        case appInstalled\n        case usableProfileData\n        case usableCookieStore\n    }\n\n    private struct CacheKey: Hashable {\n        let browser: Browser\n        let kind: ProbeKind\n    }\n\n    public init(\n        homeDirectory: String = FileManager.default.homeDirectoryForCurrentUser.path,\n        cacheTTL: TimeInterval = BrowserDetection.defaultCacheTTL,\n        now: @escaping @Sendable () -> Date = Date.init,\n        fileExists: @escaping @Sendable (String) -> Bool = { path in FileManager.default.fileExists(atPath: path) },\n        directoryContents: @escaping @Sendable (String) -> [String]? = { path in\n            try? FileManager.default.contentsOfDirectory(atPath: path)\n        })\n    {\n        self.homeDirectory = homeDirectory\n        self.cacheTTL = cacheTTL\n        self.now = now\n        self.fileExists = fileExists\n        self.directoryContents = directoryContents\n    }\n\n    public func isAppInstalled(_ browser: Browser) -> Bool {\n        // Safari is always available on macOS.\n        if browser == .safari {\n            return true\n        }\n\n        return self.cachedBool(browser: browser, kind: .appInstalled) {\n            self.detectAppInstalled(for: browser)\n        }\n    }\n\n    /// Returns true when a cookie import attempt for this browser should be allowed.\n    ///\n    /// This is intentionally stricter than `isAppInstalled`: for Chromium browsers, we only return true\n    /// when profile data exists (to avoid unnecessary Keychain prompts).\n    public func isCookieSourceAvailable(_ browser: Browser) -> Bool {\n        // We always allow Safari cookie attempts: no Keychain prompts, and it can still yield cookies\n        // even if the on-disk location changes across macOS versions.\n        if browser == .safari {\n            return true\n        }\n\n        // For browsers that typically require keychain-backed decryption, ensure an actual cookie store exists.\n        if self.requiresProfileValidation(browser) {\n            return self.hasUsableCookieStore(browser)\n        }\n\n        return self.hasUsableProfileData(browser)\n    }\n\n    public func hasUsableProfileData(_ browser: Browser) -> Bool {\n        self.cachedBool(browser: browser, kind: .usableProfileData) {\n            self.detectUsableProfileData(for: browser)\n        }\n    }\n\n    private func hasUsableCookieStore(_ browser: Browser) -> Bool {\n        self.cachedBool(browser: browser, kind: .usableCookieStore) {\n            self.detectUsableCookieStore(for: browser)\n        }\n    }\n\n    public func clearCache() {\n        self.cache.withLock { cache in\n            cache.removeAll()\n        }\n    }\n\n    // MARK: - Detection Logic\n\n    private func cachedBool(browser: Browser, kind: ProbeKind, compute: () -> Bool) -> Bool {\n        let now = self.now()\n        let key = CacheKey(browser: browser, kind: kind)\n        if let cached = self.cache.withLock({ cache in cache[key] }) {\n            if now.timeIntervalSince(cached.timestamp) < self.cacheTTL {\n                return cached.value\n            }\n        }\n\n        let result = compute()\n        self.cache.withLock { cache in\n            cache[key] = CachedResult(value: result, timestamp: now)\n        }\n        return result\n    }\n\n    private func detectAppInstalled(for browser: Browser) -> Bool {\n        let appPaths = self.applicationPaths(for: browser)\n        for path in appPaths where self.fileExists(path) {\n            return true\n        }\n        return false\n    }\n\n    private func detectUsableProfileData(for browser: Browser) -> Bool {\n        guard let profilePath = self.profilePath(for: browser, homeDirectory: self.homeDirectory) else {\n            return false\n        }\n\n        guard self.fileExists(profilePath) else {\n            return false\n        }\n\n        // For Chromium-based browsers (and Firefox), verify actual profile data exists.\n        if self.requiresProfileValidation(browser) {\n            return self.hasValidProfileDirectory(for: browser, at: profilePath)\n        }\n\n        return true\n    }\n\n    private func detectUsableCookieStore(for browser: Browser) -> Bool {\n        guard let profilePath = self.profilePath(for: browser, homeDirectory: self.homeDirectory) else {\n            return false\n        }\n\n        guard self.fileExists(profilePath) else {\n            return false\n        }\n\n        return self.hasValidCookieStore(for: browser, at: profilePath)\n    }\n\n    private func applicationPaths(for browser: Browser) -> [String] {\n        guard let appName = self.applicationName(for: browser) else { return [] }\n\n        return [\n            \"/Applications/\\(appName).app\",\n            \"\\(self.homeDirectory)/Applications/\\(appName).app\",\n        ]\n    }\n\n    private func applicationName(for browser: Browser) -> String? {\n        browser.appBundleName\n    }\n\n    private func profilePath(for browser: Browser, homeDirectory: String) -> String? {\n        if browser == .safari {\n            return \"\\(homeDirectory)/Library/Cookies/Cookies.binarycookies\"\n        }\n\n        if let relativePath = browser.chromiumProfileRelativePath {\n            return \"\\(homeDirectory)/Library/Application Support/\\(relativePath)\"\n        }\n\n        if let geckoFolder = browser.geckoProfilesFolder {\n            return \"\\(homeDirectory)/Library/Application Support/\\(geckoFolder)/Profiles\"\n        }\n\n        return nil\n    }\n\n    private func requiresProfileValidation(_ browser: Browser) -> Bool {\n        // Chromium-based browsers should have Default/ or Profile*/ subdirectories\n        if browser == .safari {\n            return false\n        }\n\n        if browser == .helium {\n            // Helium doesn't use the Default/Profile* pattern\n            return false\n        }\n\n        if browser.usesGeckoProfileStore {\n            // Firefox should have at least one *.default* directory\n            return true\n        }\n\n        if browser.usesChromiumProfileStore {\n            return true\n        }\n\n        return false\n    }\n\n    private func hasValidProfileDirectory(for browser: Browser, at profilePath: String) -> Bool {\n        guard let contents = self.directoryContents(profilePath) else { return false }\n\n        // Check for Default/ or Profile*/ subdirectories for Chromium browsers\n        let hasProfile = contents.contains { name in\n            name == \"Default\" || name.hasPrefix(\"Profile \") || name.hasPrefix(\"user-\")\n        }\n\n        if browser.usesGeckoProfileStore {\n            return contents.contains { name in\n                name.range(of: \".default\", options: [.caseInsensitive]) != nil\n            }\n        }\n\n        return hasProfile\n    }\n\n    private func hasValidCookieStore(for browser: Browser, at profilePath: String) -> Bool {\n        guard let contents = self.directoryContents(profilePath) else { return false }\n\n        if browser.usesGeckoProfileStore {\n            for name in contents where name.range(of: \".default\", options: [.caseInsensitive]) != nil {\n                let cookieDB = \"\\(profilePath)/\\(name)/cookies.sqlite\"\n                if self.fileExists(cookieDB) {\n                    return true\n                }\n            }\n            return false\n        }\n\n        for name in contents where name == \"Default\" || name.hasPrefix(\"Profile \") || name.hasPrefix(\"user-\") {\n            let cookieDBLegacy = \"\\(profilePath)/\\(name)/Cookies\"\n            let cookieDBNetwork = \"\\(profilePath)/\\(name)/Network/Cookies\"\n            if self.fileExists(cookieDBLegacy) || self.fileExists(cookieDBNetwork) {\n                return true\n            }\n        }\n\n        return false\n    }\n}\n\n#else\n\n// MARK: - Non-macOS stub\n\npublic struct BrowserDetection: Sendable {\n    public static let defaultCacheTTL: TimeInterval = 0\n\n    public init(\n        homeDirectory: String = \"\",\n        cacheTTL: TimeInterval = BrowserDetection.defaultCacheTTL,\n        now: @escaping @Sendable () -> Date = Date.init,\n        fileExists: @escaping @Sendable (String) -> Bool = { _ in false },\n        directoryContents: @escaping @Sendable (String) -> [String]? = { _ in nil })\n    {\n        _ = homeDirectory\n        _ = cacheTTL\n        _ = now\n        _ = fileExists\n        _ = directoryContents\n    }\n\n    public func isAppInstalled(_ browser: Browser) -> Bool {\n        false\n    }\n\n    public func isCookieSourceAvailable(_ browser: Browser) -> Bool {\n        false\n    }\n\n    public func hasUsableProfileData(_ browser: Browser) -> Bool {\n        false\n    }\n\n    public func clearCache() {}\n}\n\n#endif\n"
  },
  {
    "path": "Sources/CodexBarCore/Config/CodexBarConfig.swift",
    "content": "import Foundation\n\npublic struct CodexBarConfig: Codable, Sendable {\n    public static let currentVersion = 1\n\n    public var version: Int\n    public var providers: [ProviderConfig]\n\n    public init(version: Int = Self.currentVersion, providers: [ProviderConfig]) {\n        self.version = version\n        self.providers = providers\n    }\n\n    public static func makeDefault(\n        metadata: [UsageProvider: ProviderMetadata] = ProviderDescriptorRegistry.metadata) -> CodexBarConfig\n    {\n        let providers = UsageProvider.allCases.map { provider in\n            ProviderConfig(\n                id: provider,\n                enabled: metadata[provider]?.defaultEnabled)\n        }\n        return CodexBarConfig(version: Self.currentVersion, providers: providers)\n    }\n\n    public func normalized(\n        metadata: [UsageProvider: ProviderMetadata] = ProviderDescriptorRegistry.metadata) -> CodexBarConfig\n    {\n        var seen: Set<UsageProvider> = []\n        var normalized: [ProviderConfig] = []\n        normalized.reserveCapacity(max(self.providers.count, UsageProvider.allCases.count))\n\n        for provider in self.providers {\n            guard !seen.contains(provider.id) else { continue }\n            seen.insert(provider.id)\n            normalized.append(provider)\n        }\n\n        for provider in UsageProvider.allCases where !seen.contains(provider) {\n            normalized.append(ProviderConfig(\n                id: provider,\n                enabled: metadata[provider]?.defaultEnabled))\n        }\n\n        return CodexBarConfig(\n            version: Self.currentVersion,\n            providers: normalized)\n    }\n\n    public func orderedProviders() -> [UsageProvider] {\n        self.providers.map(\\.id)\n    }\n\n    public func enabledProviders(\n        metadata: [UsageProvider: ProviderMetadata] = ProviderDescriptorRegistry.metadata) -> [UsageProvider]\n    {\n        self.providers.compactMap { config in\n            let enabled = config.enabled ?? metadata[config.id]?.defaultEnabled ?? false\n            return enabled ? config.id : nil\n        }\n    }\n\n    public func providerConfig(for id: UsageProvider) -> ProviderConfig? {\n        self.providers.first(where: { $0.id == id })\n    }\n\n    public mutating func setProviderConfig(_ config: ProviderConfig) {\n        if let index = self.providers.firstIndex(where: { $0.id == config.id }) {\n            self.providers[index] = config\n        } else {\n            self.providers.append(config)\n        }\n    }\n}\n\npublic struct ProviderConfig: Codable, Sendable, Identifiable {\n    public let id: UsageProvider\n    public var enabled: Bool?\n    public var source: ProviderSourceMode?\n    public var extrasEnabled: Bool?\n    public var apiKey: String?\n    public var cookieHeader: String?\n    public var cookieSource: ProviderCookieSource?\n    public var region: String?\n    public var workspaceID: String?\n    public var tokenAccounts: ProviderTokenAccountData?\n\n    public init(\n        id: UsageProvider,\n        enabled: Bool? = nil,\n        source: ProviderSourceMode? = nil,\n        extrasEnabled: Bool? = nil,\n        apiKey: String? = nil,\n        cookieHeader: String? = nil,\n        cookieSource: ProviderCookieSource? = nil,\n        region: String? = nil,\n        workspaceID: String? = nil,\n        tokenAccounts: ProviderTokenAccountData? = nil)\n    {\n        self.id = id\n        self.enabled = enabled\n        self.source = source\n        self.extrasEnabled = extrasEnabled\n        self.apiKey = apiKey\n        self.cookieHeader = cookieHeader\n        self.cookieSource = cookieSource\n        self.region = region\n        self.workspaceID = workspaceID\n        self.tokenAccounts = tokenAccounts\n    }\n\n    public var sanitizedAPIKey: String? {\n        Self.clean(self.apiKey)\n    }\n\n    public var sanitizedCookieHeader: String? {\n        Self.clean(self.cookieHeader)\n    }\n\n    private static func clean(_ raw: String?) -> String? {\n        guard var value = raw?.trimmingCharacters(in: .whitespacesAndNewlines), !value.isEmpty else {\n            return nil\n        }\n        if (value.hasPrefix(\"\\\"\") && value.hasSuffix(\"\\\"\")) ||\n            (value.hasPrefix(\"'\") && value.hasSuffix(\"'\"))\n        {\n            value.removeFirst()\n            value.removeLast()\n        }\n        value = value.trimmingCharacters(in: .whitespacesAndNewlines)\n        return value.isEmpty ? nil : value\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Config/CodexBarConfigStore.swift",
    "content": "import Foundation\n\npublic enum CodexBarConfigStoreError: LocalizedError {\n    case invalidURL\n    case decodeFailed(String)\n    case encodeFailed(String)\n\n    public var errorDescription: String? {\n        switch self {\n        case .invalidURL:\n            \"Invalid CodexBar config path.\"\n        case let .decodeFailed(details):\n            \"Failed to decode CodexBar config: \\(details)\"\n        case let .encodeFailed(details):\n            \"Failed to encode CodexBar config: \\(details)\"\n        }\n    }\n}\n\npublic struct CodexBarConfigStore: @unchecked Sendable {\n    public let fileURL: URL\n    private let fileManager: FileManager\n\n    public init(fileURL: URL = Self.defaultURL(), fileManager: FileManager = .default) {\n        self.fileURL = fileURL\n        self.fileManager = fileManager\n    }\n\n    public func load() throws -> CodexBarConfig? {\n        guard self.fileManager.fileExists(atPath: self.fileURL.path) else { return nil }\n        let data = try Data(contentsOf: self.fileURL)\n        let decoder = JSONDecoder()\n        do {\n            let decoded = try decoder.decode(CodexBarConfig.self, from: data)\n            return decoded.normalized()\n        } catch {\n            throw CodexBarConfigStoreError.decodeFailed(error.localizedDescription)\n        }\n    }\n\n    public func loadOrCreateDefault() throws -> CodexBarConfig {\n        if let existing = try self.load() {\n            return existing\n        }\n        let config = CodexBarConfig.makeDefault()\n        try self.save(config)\n        return config\n    }\n\n    public func save(_ config: CodexBarConfig) throws {\n        let normalized = config.normalized()\n        let encoder = JSONEncoder()\n        encoder.outputFormatting = [.prettyPrinted, .sortedKeys]\n        let data: Data\n        do {\n            data = try encoder.encode(normalized)\n        } catch {\n            throw CodexBarConfigStoreError.encodeFailed(error.localizedDescription)\n        }\n        let directory = self.fileURL.deletingLastPathComponent()\n        if !self.fileManager.fileExists(atPath: directory.path) {\n            try self.fileManager.createDirectory(at: directory, withIntermediateDirectories: true)\n        }\n        try data.write(to: self.fileURL, options: [.atomic])\n        try self.applySecurePermissionsIfNeeded()\n    }\n\n    public func deleteIfPresent() throws {\n        guard self.fileManager.fileExists(atPath: self.fileURL.path) else { return }\n        try self.fileManager.removeItem(at: self.fileURL)\n    }\n\n    public static func defaultURL(home: URL = FileManager.default.homeDirectoryForCurrentUser) -> URL {\n        home\n            .appendingPathComponent(\".codexbar\", isDirectory: true)\n            .appendingPathComponent(\"config.json\")\n    }\n\n    private func applySecurePermissionsIfNeeded() throws {\n        #if os(macOS) || os(Linux)\n        try self.fileManager.setAttributes([\n            .posixPermissions: NSNumber(value: Int16(0o600)),\n        ], ofItemAtPath: self.fileURL.path)\n        #endif\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Config/CodexBarConfigValidation.swift",
    "content": "import Foundation\n\npublic enum CodexBarConfigIssueSeverity: String, Codable, Sendable {\n    case warning\n    case error\n}\n\npublic struct CodexBarConfigIssue: Codable, Sendable, Equatable {\n    public let severity: CodexBarConfigIssueSeverity\n    public let provider: UsageProvider?\n    public let field: String?\n    public let code: String\n    public let message: String\n\n    public init(\n        severity: CodexBarConfigIssueSeverity,\n        provider: UsageProvider?,\n        field: String?,\n        code: String,\n        message: String)\n    {\n        self.severity = severity\n        self.provider = provider\n        self.field = field\n        self.code = code\n        self.message = message\n    }\n}\n\npublic enum CodexBarConfigValidator {\n    public static func validate(_ config: CodexBarConfig) -> [CodexBarConfigIssue] {\n        var issues: [CodexBarConfigIssue] = []\n\n        if config.version != CodexBarConfig.currentVersion {\n            issues.append(CodexBarConfigIssue(\n                severity: .error,\n                provider: nil,\n                field: \"version\",\n                code: \"version_mismatch\",\n                message: \"Unsupported config version \\(config.version).\"))\n        }\n\n        for entry in config.providers {\n            self.validateProvider(entry, issues: &issues)\n        }\n\n        return issues\n    }\n\n    private static func validateProvider(_ entry: ProviderConfig, issues: inout [CodexBarConfigIssue]) {\n        let provider = entry.id\n        let descriptor = ProviderDescriptorRegistry.descriptor(for: provider)\n        let supportedSources = descriptor.fetchPlan.sourceModes\n        let supportsWeb = supportedSources.contains(.auto) || supportedSources.contains(.web)\n        let supportsAPI = supportedSources.contains(.api)\n\n        if let source = entry.source, !supportedSources.contains(source) {\n            issues.append(CodexBarConfigIssue(\n                severity: .error,\n                provider: provider,\n                field: \"source\",\n                code: \"unsupported_source\",\n                message: \"Source \\(source.rawValue) is not supported for \\(provider.rawValue).\"))\n        }\n\n        if let apiKey = entry.apiKey, !apiKey.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty, !supportsAPI {\n            issues.append(CodexBarConfigIssue(\n                severity: .warning,\n                provider: provider,\n                field: \"apiKey\",\n                code: \"api_key_unused\",\n                message: \"apiKey is set but \\(provider.rawValue) does not support api source.\"))\n        }\n\n        if let source = entry.source, source == .api, !supportsAPI {\n            issues.append(CodexBarConfigIssue(\n                severity: .error,\n                provider: provider,\n                field: \"source\",\n                code: \"api_source_unsupported\",\n                message: \"Source api is not supported for \\(provider.rawValue).\"))\n        }\n\n        if let source = entry.source, source == .api,\n           entry.apiKey?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ?? true\n        {\n            issues.append(CodexBarConfigIssue(\n                severity: .warning,\n                provider: provider,\n                field: \"apiKey\",\n                code: \"api_key_missing\",\n                message: \"Source api is selected but apiKey is missing for \\(provider.rawValue).\"))\n        }\n\n        if entry.cookieSource != nil, !supportsWeb {\n            issues.append(CodexBarConfigIssue(\n                severity: .warning,\n                provider: provider,\n                field: \"cookieSource\",\n                code: \"cookie_source_unused\",\n                message: \"cookieSource is set but \\(provider.rawValue) does not use web cookies.\"))\n        }\n\n        if let cookieHeader = entry.cookieHeader,\n           !cookieHeader.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty,\n           !supportsWeb\n        {\n            issues.append(CodexBarConfigIssue(\n                severity: .warning,\n                provider: provider,\n                field: \"cookieHeader\",\n                code: \"cookie_header_unused\",\n                message: \"cookieHeader is set but \\(provider.rawValue) does not use web cookies.\"))\n        }\n\n        if let cookieSource = entry.cookieSource,\n           cookieSource == .manual,\n           entry.cookieHeader?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ?? true\n        {\n            issues.append(CodexBarConfigIssue(\n                severity: .warning,\n                provider: provider,\n                field: \"cookieHeader\",\n                code: \"cookie_header_missing\",\n                message: \"cookieSource manual is set but cookieHeader is missing for \\(provider.rawValue).\"))\n        }\n\n        if let region = entry.region, !region.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {\n            switch provider {\n            case .minimax:\n                if MiniMaxAPIRegion(rawValue: region) == nil {\n                    issues.append(CodexBarConfigIssue(\n                        severity: .error,\n                        provider: provider,\n                        field: \"region\",\n                        code: \"invalid_region\",\n                        message: \"Region \\(region) is not a valid MiniMax region.\"))\n                }\n            case .zai:\n                if ZaiAPIRegion(rawValue: region) == nil {\n                    issues.append(CodexBarConfigIssue(\n                        severity: .error,\n                        provider: provider,\n                        field: \"region\",\n                        code: \"invalid_region\",\n                        message: \"Region \\(region) is not a valid z.ai region.\"))\n                }\n            case .alibaba:\n                if AlibabaCodingPlanAPIRegion(rawValue: region) == nil {\n                    issues.append(CodexBarConfigIssue(\n                        severity: .error,\n                        provider: provider,\n                        field: \"region\",\n                        code: \"invalid_region\",\n                        message: \"Region \\(region) is not a valid Alibaba Coding Plan region.\"))\n                }\n            default:\n                issues.append(CodexBarConfigIssue(\n                    severity: .warning,\n                    provider: provider,\n                    field: \"region\",\n                    code: \"region_unused\",\n                    message: \"region is set but \\(provider.rawValue) does not use regions.\"))\n            }\n        }\n\n        if let workspaceID = entry.workspaceID,\n           !workspaceID.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty,\n           provider != .opencode\n        {\n            issues.append(CodexBarConfigIssue(\n                severity: .warning,\n                provider: provider,\n                field: \"workspaceID\",\n                code: \"workspace_unused\",\n                message: \"workspaceID is set but only opencode supports workspaceID.\"))\n        }\n\n        if let tokenAccounts = entry.tokenAccounts, !tokenAccounts.accounts.isEmpty,\n           TokenAccountSupportCatalog.support(for: provider) == nil\n        {\n            issues.append(CodexBarConfigIssue(\n                severity: .warning,\n                provider: provider,\n                field: \"tokenAccounts\",\n                code: \"token_accounts_unused\",\n                message: \"tokenAccounts are set but \\(provider.rawValue) does not support token accounts.\"))\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Config/ProviderConfigEnvironment.swift",
    "content": "import Foundation\n\npublic enum ProviderConfigEnvironment {\n    public static func applyAPIKeyOverride(\n        base: [String: String],\n        provider: UsageProvider,\n        config: ProviderConfig?) -> [String: String]\n    {\n        guard let apiKey = config?.sanitizedAPIKey, !apiKey.isEmpty else { return base }\n        var env = base\n        switch provider {\n        case .zai:\n            env[ZaiSettingsReader.apiTokenKey] = apiKey\n        case .copilot:\n            env[\"COPILOT_API_TOKEN\"] = apiKey\n        case .minimax:\n            env[MiniMaxAPISettingsReader.apiTokenKey] = apiKey\n        case .alibaba:\n            env[AlibabaCodingPlanSettingsReader.apiTokenKey] = apiKey\n        case .kilo:\n            env[KiloSettingsReader.apiTokenKey] = apiKey\n        case .kimik2:\n            if let key = KimiK2SettingsReader.apiKeyEnvironmentKeys.first {\n                env[key] = apiKey\n            }\n        case .synthetic:\n            env[SyntheticSettingsReader.apiKeyKey] = apiKey\n        case .warp:\n            if let key = WarpSettingsReader.apiKeyEnvironmentKeys.first {\n                env[key] = apiKey\n            }\n        case .openrouter:\n            env[OpenRouterSettingsReader.envKey] = apiKey\n        default:\n            break\n        }\n        return env\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/CookieHeaderCache.swift",
    "content": "import Foundation\n\npublic enum CookieHeaderCache {\n    public struct Entry: Codable, Sendable {\n        public let cookieHeader: String\n        public let storedAt: Date\n        public let sourceLabel: String\n\n        public init(cookieHeader: String, storedAt: Date, sourceLabel: String) {\n            self.cookieHeader = cookieHeader\n            self.storedAt = storedAt\n            self.sourceLabel = sourceLabel\n        }\n    }\n\n    private static let log = CodexBarLog.logger(LogCategories.cookieCache)\n    private nonisolated(unsafe) static var legacyBaseURLOverride: URL?\n\n    public static func load(provider: UsageProvider) -> Entry? {\n        let key = KeychainCacheStore.Key.cookie(provider: provider)\n        switch KeychainCacheStore.load(key: key, as: Entry.self) {\n        case let .found(entry):\n            self.log.debug(\"Cookie cache hit\", metadata: [\"provider\": provider.rawValue])\n            return entry\n        case .invalid:\n            self.log.warning(\"Cookie cache invalid; clearing\", metadata: [\"provider\": provider.rawValue])\n            KeychainCacheStore.clear(key: key)\n        case .missing:\n            self.log.debug(\"Cookie cache miss\", metadata: [\"provider\": provider.rawValue])\n        }\n\n        guard let legacy = self.loadLegacyEntry(for: provider) else { return nil }\n        KeychainCacheStore.store(key: key, entry: legacy)\n        self.removeLegacyEntry(for: provider)\n        self.log.debug(\"Cookie cache migrated from legacy store\", metadata: [\"provider\": provider.rawValue])\n        return legacy\n    }\n\n    public static func store(\n        provider: UsageProvider,\n        cookieHeader: String,\n        sourceLabel: String,\n        now: Date = Date())\n    {\n        let trimmed = cookieHeader.trimmingCharacters(in: .whitespacesAndNewlines)\n        guard let normalized = CookieHeaderNormalizer.normalize(trimmed), !normalized.isEmpty else {\n            self.clear(provider: provider)\n            return\n        }\n        let entry = Entry(cookieHeader: normalized, storedAt: now, sourceLabel: sourceLabel)\n        let key = KeychainCacheStore.Key.cookie(provider: provider)\n        KeychainCacheStore.store(key: key, entry: entry)\n        self.removeLegacyEntry(for: provider)\n        self.log.debug(\"Cookie cache stored\", metadata: [\"provider\": provider.rawValue, \"source\": sourceLabel])\n    }\n\n    public static func clear(provider: UsageProvider) {\n        let key = KeychainCacheStore.Key.cookie(provider: provider)\n        KeychainCacheStore.clear(key: key)\n        self.removeLegacyEntry(for: provider)\n        self.log.debug(\"Cookie cache cleared\", metadata: [\"provider\": provider.rawValue])\n    }\n\n    static func load(from url: URL) -> Entry? {\n        guard let data = try? Data(contentsOf: url) else { return nil }\n        let decoder = JSONDecoder()\n        decoder.dateDecodingStrategy = .iso8601\n        return try? decoder.decode(Entry.self, from: data)\n    }\n\n    static func store(_ entry: Entry, to url: URL) {\n        do {\n            let dir = url.deletingLastPathComponent()\n            try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)\n            let encoder = JSONEncoder()\n            encoder.dateEncodingStrategy = .iso8601\n            let data = try encoder.encode(entry)\n            try data.write(to: url, options: [.atomic])\n        } catch {\n            self.log.error(\"Failed to persist cookie cache: \\(error)\")\n        }\n    }\n\n    static func setLegacyBaseURLOverrideForTesting(_ url: URL?) {\n        self.legacyBaseURLOverride = url\n    }\n\n    private static func loadLegacyEntry(for provider: UsageProvider) -> Entry? {\n        self.load(from: self.legacyURL(for: provider))\n    }\n\n    private static func removeLegacyEntry(for provider: UsageProvider) {\n        let url = self.legacyURL(for: provider)\n        do {\n            try FileManager.default.removeItem(at: url)\n        } catch {\n            if (error as NSError).code != NSFileNoSuchFileError {\n                Self.log.error(\"Failed to remove cookie cache (\\(provider.rawValue)): \\(error)\")\n            }\n        }\n    }\n\n    private static func legacyURL(for provider: UsageProvider) -> URL {\n        if let override = self.legacyBaseURLOverride {\n            return override.appendingPathComponent(\"\\(provider.rawValue)-cookie.json\")\n        }\n        let fm = FileManager.default\n        let base = fm.urls(for: .applicationSupportDirectory, in: .userDomainMask).first\n            ?? fm.temporaryDirectory\n        return base.appendingPathComponent(\"CodexBar\", isDirectory: true)\n            .appendingPathComponent(\"\\(provider.rawValue)-cookie.json\")\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/CookieHeaderNormalizer.swift",
    "content": "import Foundation\n\npublic enum CookieHeaderNormalizer {\n    private static let headerPatterns: [String] = [\n        #\"(?i)-H\\s*'Cookie:\\s*([^']+)'\"#,\n        #\"(?i)-H\\s*\\\"Cookie:\\s*([^\\\"]+)\\\"\"#,\n        #\"(?i)\\bcookie:\\s*'([^']+)'\"#,\n        #\"(?i)\\bcookie:\\s*\\\"([^\\\"]+)\\\"\"#,\n        #\"(?i)\\bcookie:\\s*([^\\r\\n]+)\"#,\n        #\"(?i)(?:^|\\s)(?:--cookie|-b)\\s*'([^']+)'\"#,\n        #\"(?i)(?:^|\\s)(?:--cookie|-b)\\s*\\\"([^\\\"]+)\\\"\"#,\n        #\"(?i)(?:^|\\s)-b([^\\s=]+=[^\\s]+)\"#,\n        #\"(?i)(?:^|\\s)(?:--cookie|-b)\\s+([^\\s]+)\"#,\n    ]\n\n    public static func normalize(_ raw: String?) -> String? {\n        guard var value = raw?.trimmingCharacters(in: .whitespacesAndNewlines), !value.isEmpty else {\n            return nil\n        }\n\n        if let extracted = self.extractHeader(from: value) {\n            value = extracted\n        }\n\n        value = self.stripCookiePrefix(value)\n        value = self.stripWrappingQuotes(value)\n        value = value.trimmingCharacters(in: .whitespacesAndNewlines)\n\n        return value.isEmpty ? nil : value\n    }\n\n    public static func pairs(from raw: String) -> [(name: String, value: String)] {\n        guard let normalized = self.normalize(raw) else { return [] }\n        var results: [(name: String, value: String)] = []\n        results.reserveCapacity(6)\n\n        for part in normalized.split(separator: \";\") {\n            let trimmed = part.trimmingCharacters(in: .whitespacesAndNewlines)\n            guard !trimmed.isEmpty,\n                  let equalsIndex = trimmed.firstIndex(of: \"=\")\n            else {\n                continue\n            }\n            let name = trimmed[..<equalsIndex].trimmingCharacters(in: .whitespacesAndNewlines)\n            let value = trimmed[trimmed.index(after: equalsIndex)...]\n                .trimmingCharacters(in: .whitespacesAndNewlines)\n            guard !name.isEmpty else { continue }\n            results.append((name: String(name), value: String(value)))\n        }\n\n        return results\n    }\n\n    private static func extractHeader(from raw: String) -> String? {\n        for pattern in self.headerPatterns {\n            guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { continue }\n            let range = NSRange(raw.startIndex..<raw.endIndex, in: raw)\n            guard let match = regex.firstMatch(in: raw, options: [], range: range),\n                  match.numberOfRanges >= 2,\n                  let captureRange = Range(match.range(at: 1), in: raw)\n            else {\n                continue\n            }\n            let captured = raw[captureRange].trimmingCharacters(in: .whitespacesAndNewlines)\n            if !captured.isEmpty { return String(captured) }\n        }\n        return nil\n    }\n\n    private static func stripCookiePrefix(_ raw: String) -> String {\n        let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)\n        guard trimmed.lowercased().hasPrefix(\"cookie:\") else { return trimmed }\n        let idx = trimmed.index(trimmed.startIndex, offsetBy: \"cookie:\".count)\n        return String(trimmed[idx...]).trimmingCharacters(in: .whitespacesAndNewlines)\n    }\n\n    private static func stripWrappingQuotes(_ raw: String) -> String {\n        guard raw.count >= 2 else { return raw }\n        if (raw.hasPrefix(\"\\\"\") && raw.hasSuffix(\"\\\"\")) ||\n            (raw.hasPrefix(\"'\") && raw.hasSuffix(\"'\"))\n        {\n            return String(raw.dropFirst().dropLast())\n        }\n        return raw\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/CopilotUsageModels.swift",
    "content": "import Foundation\n\npublic struct CopilotUsageResponse: Sendable, Decodable {\n    private struct AnyCodingKey: CodingKey {\n        let stringValue: String\n        let intValue: Int?\n\n        init?(stringValue: String) {\n            self.stringValue = stringValue\n            self.intValue = nil\n        }\n\n        init?(intValue: Int) {\n            self.stringValue = String(intValue)\n            self.intValue = intValue\n        }\n    }\n\n    public struct QuotaSnapshot: Sendable, Decodable {\n        public let entitlement: Double\n        public let remaining: Double\n        public let percentRemaining: Double\n        public let quotaId: String\n        public let hasPercentRemaining: Bool\n        public var isPlaceholder: Bool {\n            self.entitlement == 0 && self.remaining == 0 && self.percentRemaining == 0 && self.quotaId.isEmpty\n        }\n\n        private enum CodingKeys: String, CodingKey {\n            case entitlement\n            case remaining\n            case percentRemaining = \"percent_remaining\"\n            case quotaId = \"quota_id\"\n        }\n\n        public init(\n            entitlement: Double,\n            remaining: Double,\n            percentRemaining: Double,\n            quotaId: String,\n            hasPercentRemaining: Bool = true)\n        {\n            self.entitlement = entitlement\n            self.remaining = remaining\n            self.percentRemaining = percentRemaining\n            self.quotaId = quotaId\n            self.hasPercentRemaining = hasPercentRemaining\n        }\n\n        public init(from decoder: any Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            let decodedEntitlement = Self.decodeNumberIfPresent(container: container, key: .entitlement)\n            let decodedRemaining = Self.decodeNumberIfPresent(container: container, key: .remaining)\n            self.entitlement = decodedEntitlement ?? 0\n            self.remaining = decodedRemaining ?? 0\n            let decodedPercent = Self.decodeNumberIfPresent(container: container, key: .percentRemaining)\n            if let decodedPercent {\n                self.percentRemaining = max(0, min(100, decodedPercent))\n                self.hasPercentRemaining = true\n            } else if let entitlement = decodedEntitlement,\n                      entitlement > 0,\n                      let remaining = decodedRemaining\n            {\n                let derived = (remaining / entitlement) * 100\n                self.percentRemaining = max(0, min(100, derived))\n                self.hasPercentRemaining = true\n            } else {\n                // Without percent_remaining and both inputs for derivation, the percent is unknown.\n                self.percentRemaining = 0\n                self.hasPercentRemaining = false\n            }\n            self.quotaId = try container.decodeIfPresent(String.self, forKey: .quotaId) ?? \"\"\n        }\n\n        private static func decodeNumberIfPresent(\n            container: KeyedDecodingContainer<CodingKeys>,\n            key: CodingKeys) -> Double?\n        {\n            if let value = try? container.decodeIfPresent(Double.self, forKey: key) {\n                return value\n            }\n            if let value = try? container.decodeIfPresent(Int.self, forKey: key) {\n                return Double(value)\n            }\n            if let value = try? container.decodeIfPresent(String.self, forKey: key) {\n                return Double(value)\n            }\n            return nil\n        }\n    }\n\n    public struct QuotaCounts: Sendable, Decodable {\n        public let chat: Double?\n        public let completions: Double?\n\n        private enum CodingKeys: String, CodingKey {\n            case chat\n            case completions\n        }\n\n        public init(chat: Double?, completions: Double?) {\n            self.chat = chat\n            self.completions = completions\n        }\n\n        public init(from decoder: any Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            self.chat = Self.decodeNumberIfPresent(container: container, key: .chat)\n            self.completions = Self.decodeNumberIfPresent(container: container, key: .completions)\n        }\n\n        private static func decodeNumberIfPresent(\n            container: KeyedDecodingContainer<CodingKeys>,\n            key: CodingKeys) -> Double?\n        {\n            if let value = try? container.decodeIfPresent(Double.self, forKey: key) {\n                return value\n            }\n            if let value = try? container.decodeIfPresent(Int.self, forKey: key) {\n                return Double(value)\n            }\n            if let value = try? container.decodeIfPresent(String.self, forKey: key) {\n                return Double(value)\n            }\n            return nil\n        }\n    }\n\n    public struct QuotaSnapshots: Sendable, Decodable {\n        public let premiumInteractions: QuotaSnapshot?\n        public let chat: QuotaSnapshot?\n\n        private enum CodingKeys: String, CodingKey {\n            case premiumInteractions = \"premium_interactions\"\n            case chat\n        }\n\n        public init(premiumInteractions: QuotaSnapshot?, chat: QuotaSnapshot?) {\n            self.premiumInteractions = premiumInteractions\n            self.chat = chat\n        }\n\n        public init(from decoder: any Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            var premium = try container.decodeIfPresent(QuotaSnapshot.self, forKey: .premiumInteractions)\n            var chat = try container.decodeIfPresent(QuotaSnapshot.self, forKey: .chat)\n            if premium?.isPlaceholder == true {\n                premium = nil\n            }\n            if chat?.isPlaceholder == true {\n                chat = nil\n            }\n\n            if premium == nil || chat == nil {\n                let dynamic = try decoder.container(keyedBy: AnyCodingKey.self)\n                var fallbackPremium: QuotaSnapshot?\n                var fallbackChat: QuotaSnapshot?\n                var firstUsable: QuotaSnapshot?\n\n                for key in dynamic.allKeys {\n                    let value: QuotaSnapshot\n                    do {\n                        guard let decoded = try dynamic.decodeIfPresent(QuotaSnapshot.self, forKey: key) else {\n                            continue\n                        }\n                        guard !decoded.isPlaceholder else { continue }\n                        value = decoded\n                    } catch {\n                        continue\n                    }\n\n                    let name = key.stringValue.lowercased()\n                    if firstUsable == nil {\n                        firstUsable = value\n                    }\n\n                    if fallbackChat == nil, name.contains(\"chat\") {\n                        fallbackChat = value\n                        continue\n                    }\n\n                    if fallbackPremium == nil,\n                       name.contains(\"premium\") || name.contains(\"completion\") || name.contains(\"code\")\n                    {\n                        fallbackPremium = value\n                    }\n                }\n\n                if premium == nil {\n                    premium = fallbackPremium\n                }\n                if chat == nil {\n                    chat = fallbackChat\n                }\n                if premium == nil, chat == nil {\n                    // If keys are unfamiliar, still expose one usable quota instead of failing.\n                    chat = firstUsable\n                }\n            }\n\n            self.premiumInteractions = premium\n            self.chat = chat\n        }\n    }\n\n    public let quotaSnapshots: QuotaSnapshots\n    public let copilotPlan: String\n    public let assignedDate: String?\n    public let quotaResetDate: String?\n\n    private enum CodingKeys: String, CodingKey {\n        case quotaSnapshots = \"quota_snapshots\"\n        case copilotPlan = \"copilot_plan\"\n        case assignedDate = \"assigned_date\"\n        case quotaResetDate = \"quota_reset_date\"\n        case monthlyQuotas = \"monthly_quotas\"\n        case limitedUserQuotas = \"limited_user_quotas\"\n    }\n\n    public init(\n        quotaSnapshots: QuotaSnapshots,\n        copilotPlan: String,\n        assignedDate: String?,\n        quotaResetDate: String?)\n    {\n        self.quotaSnapshots = quotaSnapshots\n        self.copilotPlan = copilotPlan\n        self.assignedDate = assignedDate\n        self.quotaResetDate = quotaResetDate\n    }\n\n    public init(from decoder: any Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        let directSnapshots = try container.decodeIfPresent(QuotaSnapshots.self, forKey: .quotaSnapshots)\n        let monthlyQuotas = try container.decodeIfPresent(QuotaCounts.self, forKey: .monthlyQuotas)\n        let limitedUserQuotas = try container.decodeIfPresent(QuotaCounts.self, forKey: .limitedUserQuotas)\n        let monthlyLimitedSnapshots = Self.makeQuotaSnapshots(monthly: monthlyQuotas, limited: limitedUserQuotas)\n        let premium = Self.usableQuotaSnapshot(from: directSnapshots?.premiumInteractions) ??\n            Self.usableQuotaSnapshot(from: monthlyLimitedSnapshots?.premiumInteractions)\n        let chat = Self.usableQuotaSnapshot(from: directSnapshots?.chat) ??\n            Self.usableQuotaSnapshot(from: monthlyLimitedSnapshots?.chat)\n        if premium != nil || chat != nil {\n            self.quotaSnapshots = QuotaSnapshots(premiumInteractions: premium, chat: chat)\n        } else {\n            self.quotaSnapshots = directSnapshots ?? QuotaSnapshots(premiumInteractions: nil, chat: nil)\n        }\n        self.copilotPlan = try container.decodeIfPresent(String.self, forKey: .copilotPlan) ?? \"unknown\"\n        self.assignedDate = try container.decodeIfPresent(String.self, forKey: .assignedDate)\n        self.quotaResetDate = try container.decodeIfPresent(String.self, forKey: .quotaResetDate)\n    }\n\n    private static func makeQuotaSnapshots(monthly: QuotaCounts?, limited: QuotaCounts?) -> QuotaSnapshots? {\n        let premium = Self.makeQuotaSnapshot(\n            monthly: monthly?.completions,\n            limited: limited?.completions,\n            quotaID: \"completions\")\n        let chat = Self.makeQuotaSnapshot(\n            monthly: monthly?.chat,\n            limited: limited?.chat,\n            quotaID: \"chat\")\n        guard premium != nil || chat != nil else { return nil }\n        return QuotaSnapshots(premiumInteractions: premium, chat: chat)\n    }\n\n    private static func makeQuotaSnapshot(monthly: Double?, limited: Double?, quotaID: String) -> QuotaSnapshot? {\n        guard monthly != nil || limited != nil else { return nil }\n        guard let monthly else {\n            // Without a monthly denominator, avoid fabricating a misleading percentage.\n            return nil\n        }\n        guard let limited else {\n            // Without the limited/remaining value, usage is unknown.\n            return nil\n        }\n\n        let entitlement = max(0, monthly)\n        guard entitlement > 0 else {\n            // A zero denominator cannot produce a meaningful percentage.\n            return nil\n        }\n        let remaining = max(0, limited)\n        let percentRemaining = max(0, min(100, (remaining / entitlement) * 100))\n\n        return QuotaSnapshot(\n            entitlement: entitlement,\n            remaining: remaining,\n            percentRemaining: percentRemaining,\n            quotaId: quotaID)\n    }\n\n    private static func usableQuotaSnapshot(from snapshot: QuotaSnapshot?) -> QuotaSnapshot? {\n        guard let snapshot, !snapshot.isPlaceholder, snapshot.hasPercentRemaining else {\n            return nil\n        }\n        return snapshot\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/CostUsageFetcher.swift",
    "content": "import Foundation\n\npublic enum CostUsageError: LocalizedError, Sendable {\n    case unsupportedProvider(UsageProvider)\n    case timedOut(seconds: Int)\n\n    public var errorDescription: String? {\n        switch self {\n        case let .unsupportedProvider(provider):\n            return \"Cost summary is not supported for \\(provider.rawValue).\"\n        case let .timedOut(seconds):\n            if seconds >= 60, seconds % 60 == 0 {\n                return \"Cost refresh timed out after \\(seconds / 60)m.\"\n            }\n            return \"Cost refresh timed out after \\(seconds)s.\"\n        }\n    }\n}\n\npublic struct CostUsageFetcher: Sendable {\n    public init() {}\n\n    public func loadTokenSnapshot(\n        provider: UsageProvider,\n        now: Date = Date(),\n        forceRefresh: Bool = false,\n        allowVertexClaudeFallback: Bool = false) async throws -> CostUsageTokenSnapshot\n    {\n        guard provider == .codex || provider == .claude || provider == .vertexai else {\n            throw CostUsageError.unsupportedProvider(provider)\n        }\n\n        let until = now\n        // Rolling window: last 30 days (inclusive). Use -29 for inclusive boundaries.\n        let since = Calendar.current.date(byAdding: .day, value: -29, to: now) ?? now\n\n        var options = CostUsageScanner.Options()\n        if provider == .vertexai {\n            options.claudeLogProviderFilter = allowVertexClaudeFallback ? .all : .vertexAIOnly\n        } else if provider == .claude {\n            options.claudeLogProviderFilter = .excludeVertexAI\n        }\n        if forceRefresh {\n            options.refreshMinIntervalSeconds = 0\n            options.forceRescan = true\n        }\n        var daily = CostUsageScanner.loadDailyReport(\n            provider: provider,\n            since: since,\n            until: until,\n            now: now,\n            options: options)\n\n        if provider == .vertexai,\n           !allowVertexClaudeFallback,\n           options.claudeLogProviderFilter == .vertexAIOnly,\n           daily.data.isEmpty\n        {\n            var fallback = options\n            fallback.claudeLogProviderFilter = .all\n            daily = CostUsageScanner.loadDailyReport(\n                provider: provider,\n                since: since,\n                until: until,\n                now: now,\n                options: fallback)\n        }\n\n        return Self.tokenSnapshot(from: daily, now: now)\n    }\n\n    static func tokenSnapshot(from daily: CostUsageDailyReport, now: Date) -> CostUsageTokenSnapshot {\n        // Pick the most recent day; break ties by cost/tokens to keep a stable \"session\" row.\n        let currentDay = daily.data.max { lhs, rhs in\n            let lDate = CostUsageDateParser.parse(lhs.date) ?? .distantPast\n            let rDate = CostUsageDateParser.parse(rhs.date) ?? .distantPast\n            if lDate != rDate { return lDate < rDate }\n            let lCost = lhs.costUSD ?? -1\n            let rCost = rhs.costUSD ?? -1\n            if lCost != rCost { return lCost < rCost }\n            let lTokens = lhs.totalTokens ?? -1\n            let rTokens = rhs.totalTokens ?? -1\n            if lTokens != rTokens { return lTokens < rTokens }\n            return lhs.date < rhs.date\n        }\n        // Prefer summary totals when present; fall back to summing daily entries.\n        let totalFromSummary = daily.summary?.totalCostUSD\n        let totalFromEntries = daily.data.compactMap(\\.costUSD).reduce(0, +)\n        let last30DaysCostUSD = totalFromSummary ?? (totalFromEntries > 0 ? totalFromEntries : nil)\n        let totalTokensFromSummary = daily.summary?.totalTokens\n        let totalTokensFromEntries = daily.data.compactMap(\\.totalTokens).reduce(0, +)\n        let last30DaysTokens = totalTokensFromSummary ?? (totalTokensFromEntries > 0 ? totalTokensFromEntries : nil)\n\n        return CostUsageTokenSnapshot(\n            sessionTokens: currentDay?.totalTokens,\n            sessionCostUSD: currentDay?.costUSD,\n            last30DaysTokens: last30DaysTokens,\n            last30DaysCostUSD: last30DaysCostUSD,\n            daily: daily.data,\n            updatedAt: now)\n    }\n\n    static func selectCurrentSession(from sessions: [CostUsageSessionReport.Entry])\n        -> CostUsageSessionReport.Entry?\n    {\n        if sessions.isEmpty { return nil }\n        return sessions.max { lhs, rhs in\n            let lDate = CostUsageDateParser.parse(lhs.lastActivity) ?? .distantPast\n            let rDate = CostUsageDateParser.parse(rhs.lastActivity) ?? .distantPast\n            if lDate != rDate { return lDate < rDate }\n            let lCost = lhs.costUSD ?? -1\n            let rCost = rhs.costUSD ?? -1\n            if lCost != rCost { return lCost < rCost }\n            let lTokens = lhs.totalTokens ?? -1\n            let rTokens = rhs.totalTokens ?? -1\n            if lTokens != rTokens { return lTokens < rTokens }\n            return lhs.session < rhs.session\n        }\n    }\n\n    static func selectMostRecentMonth(from months: [CostUsageMonthlyReport.Entry])\n        -> CostUsageMonthlyReport.Entry?\n    {\n        if months.isEmpty { return nil }\n        return months.max { lhs, rhs in\n            let lDate = CostUsageDateParser.parseMonth(lhs.month) ?? .distantPast\n            let rDate = CostUsageDateParser.parseMonth(rhs.month) ?? .distantPast\n            if lDate != rDate { return lDate < rDate }\n            let lCost = lhs.costUSD ?? -1\n            let rCost = rhs.costUSD ?? -1\n            if lCost != rCost { return lCost < rCost }\n            let lTokens = lhs.totalTokens ?? -1\n            let rTokens = rhs.totalTokens ?? -1\n            if lTokens != rTokens { return lTokens < rTokens }\n            return lhs.month < rhs.month\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/CostUsageModels.swift",
    "content": "import Foundation\n\npublic struct CostUsageTokenSnapshot: Sendable, Equatable {\n    public let sessionTokens: Int?\n    public let sessionCostUSD: Double?\n    public let last30DaysTokens: Int?\n    public let last30DaysCostUSD: Double?\n    public let daily: [CostUsageDailyReport.Entry]\n    public let updatedAt: Date\n\n    public init(\n        sessionTokens: Int?,\n        sessionCostUSD: Double?,\n        last30DaysTokens: Int?,\n        last30DaysCostUSD: Double?,\n        daily: [CostUsageDailyReport.Entry],\n        updatedAt: Date)\n    {\n        self.sessionTokens = sessionTokens\n        self.sessionCostUSD = sessionCostUSD\n        self.last30DaysTokens = last30DaysTokens\n        self.last30DaysCostUSD = last30DaysCostUSD\n        self.daily = daily\n        self.updatedAt = updatedAt\n    }\n}\n\npublic struct CostUsageDailyReport: Sendable, Decodable {\n    public struct ModelBreakdown: Sendable, Decodable, Equatable {\n        public let modelName: String\n        public let costUSD: Double?\n        public let totalTokens: Int?\n\n        private enum CodingKeys: String, CodingKey {\n            case modelName\n            case costUSD\n            case cost\n            case totalTokens\n        }\n\n        public init(from decoder: Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            self.modelName = try container.decode(String.self, forKey: .modelName)\n            self.costUSD =\n                try container.decodeIfPresent(Double.self, forKey: .costUSD)\n                ?? container.decodeIfPresent(Double.self, forKey: .cost)\n            self.totalTokens = try container.decodeIfPresent(Int.self, forKey: .totalTokens)\n        }\n\n        public init(modelName: String, costUSD: Double?, totalTokens: Int? = nil) {\n            self.modelName = modelName\n            self.costUSD = costUSD\n            self.totalTokens = totalTokens\n        }\n    }\n\n    public struct Entry: Sendable, Decodable, Equatable {\n        public let date: String\n        public let inputTokens: Int?\n        public let cacheReadTokens: Int?\n        public let cacheCreationTokens: Int?\n        public let outputTokens: Int?\n        public let totalTokens: Int?\n        public let costUSD: Double?\n        public let modelsUsed: [String]?\n        public let modelBreakdowns: [ModelBreakdown]?\n\n        private enum CodingKeys: String, CodingKey {\n            case date\n            case inputTokens\n            case cacheReadTokens\n            case cacheCreationTokens\n            case cacheReadInputTokens\n            case cacheCreationInputTokens\n            case outputTokens\n            case totalTokens\n            case costUSD\n            case totalCost\n            case modelsUsed\n            case models\n            case modelBreakdowns\n        }\n\n        public init(from decoder: Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            self.date = try container.decode(String.self, forKey: .date)\n            self.inputTokens = try container.decodeIfPresent(Int.self, forKey: .inputTokens)\n            self.cacheReadTokens =\n                try container.decodeIfPresent(Int.self, forKey: .cacheReadTokens)\n                ?? container.decodeIfPresent(Int.self, forKey: .cacheReadInputTokens)\n            self.cacheCreationTokens =\n                try container.decodeIfPresent(Int.self, forKey: .cacheCreationTokens)\n                ?? container.decodeIfPresent(Int.self, forKey: .cacheCreationInputTokens)\n            self.outputTokens = try container.decodeIfPresent(Int.self, forKey: .outputTokens)\n            self.totalTokens = try container.decodeIfPresent(Int.self, forKey: .totalTokens)\n            self.costUSD =\n                try container.decodeIfPresent(Double.self, forKey: .costUSD)\n                ?? container.decodeIfPresent(Double.self, forKey: .totalCost)\n            self.modelsUsed = Self.decodeModelsUsed(from: container)\n            self.modelBreakdowns = try container.decodeIfPresent([ModelBreakdown].self, forKey: .modelBreakdowns)\n        }\n\n        public init(\n            date: String,\n            inputTokens: Int?,\n            outputTokens: Int?,\n            cacheReadTokens: Int? = nil,\n            cacheCreationTokens: Int? = nil,\n            totalTokens: Int?,\n            costUSD: Double?,\n            modelsUsed: [String]?,\n            modelBreakdowns: [ModelBreakdown]?)\n        {\n            self.date = date\n            self.inputTokens = inputTokens\n            self.outputTokens = outputTokens\n            self.cacheReadTokens = cacheReadTokens\n            self.cacheCreationTokens = cacheCreationTokens\n            self.totalTokens = totalTokens\n            self.costUSD = costUSD\n            self.modelsUsed = modelsUsed\n            self.modelBreakdowns = modelBreakdowns\n        }\n\n        private static func decodeModelsUsed(from container: KeyedDecodingContainer<CodingKeys>) -> [String]? {\n            func decodeStringList(_ key: CodingKeys) -> [String]? {\n                (try? container.decodeIfPresent([String].self, forKey: key)).flatMap(\\.self)\n            }\n\n            if let modelsUsed = decodeStringList(.modelsUsed) { return modelsUsed }\n            if let models = decodeStringList(.models) { return models }\n\n            guard container.contains(.models) else { return nil }\n\n            guard let modelMap = try? container.nestedContainer(keyedBy: CostUsageAnyCodingKey.self, forKey: .models)\n            else { return nil }\n\n            let modelNames = modelMap.allKeys.map(\\.stringValue).sorted()\n            return modelNames.isEmpty ? nil : modelNames\n        }\n    }\n\n    public struct Summary: Sendable, Decodable, Equatable {\n        public let totalInputTokens: Int?\n        public let totalOutputTokens: Int?\n        public let cacheReadTokens: Int?\n        public let cacheCreationTokens: Int?\n        public let totalTokens: Int?\n        public let totalCostUSD: Double?\n\n        private enum CodingKeys: String, CodingKey {\n            case totalInputTokens\n            case totalOutputTokens\n            case cacheReadTokens\n            case cacheCreationTokens\n            case totalCacheReadTokens\n            case totalCacheCreationTokens\n            case totalTokens\n            case totalCostUSD\n            case totalCost\n        }\n\n        public init(\n            totalInputTokens: Int?,\n            totalOutputTokens: Int?,\n            cacheReadTokens: Int? = nil,\n            cacheCreationTokens: Int? = nil,\n            totalTokens: Int?,\n            totalCostUSD: Double?)\n        {\n            self.totalInputTokens = totalInputTokens\n            self.totalOutputTokens = totalOutputTokens\n            self.cacheReadTokens = cacheReadTokens\n            self.cacheCreationTokens = cacheCreationTokens\n            self.totalTokens = totalTokens\n            self.totalCostUSD = totalCostUSD\n        }\n\n        public init(from decoder: Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            self.totalInputTokens = try container.decodeIfPresent(Int.self, forKey: .totalInputTokens)\n            self.totalOutputTokens = try container.decodeIfPresent(Int.self, forKey: .totalOutputTokens)\n            self.cacheReadTokens =\n                try container.decodeIfPresent(Int.self, forKey: .cacheReadTokens)\n                ?? container.decodeIfPresent(Int.self, forKey: .totalCacheReadTokens)\n            self.cacheCreationTokens =\n                try container.decodeIfPresent(Int.self, forKey: .cacheCreationTokens)\n                ?? container.decodeIfPresent(Int.self, forKey: .totalCacheCreationTokens)\n            self.totalTokens = try container.decodeIfPresent(Int.self, forKey: .totalTokens)\n            self.totalCostUSD =\n                try container.decodeIfPresent(Double.self, forKey: .totalCostUSD)\n                ?? container.decodeIfPresent(Double.self, forKey: .totalCost)\n        }\n    }\n\n    public let data: [Entry]\n    public let summary: Summary?\n\n    private enum CodingKeys: String, CodingKey {\n        case type\n        case data\n        case summary\n        case daily\n        case totals\n    }\n\n    public init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n\n        if container.contains(.type) {\n            _ = try container.decode(String.self, forKey: .type)\n            self.data = try container.decode([Entry].self, forKey: .data)\n            self.summary = try container.decodeIfPresent(Summary.self, forKey: .summary)\n            return\n        }\n\n        self.data = try container.decode([Entry].self, forKey: .daily)\n        if container.contains(.totals) {\n            let totals = try container.decode(CostUsageLegacyTotals.self, forKey: .totals)\n            self.summary = Summary(\n                totalInputTokens: totals.totalInputTokens,\n                totalOutputTokens: totals.totalOutputTokens,\n                cacheReadTokens: totals.cacheReadTokens,\n                cacheCreationTokens: totals.cacheCreationTokens,\n                totalTokens: totals.totalTokens,\n                totalCostUSD: totals.totalCost)\n        } else {\n            self.summary = nil\n        }\n    }\n\n    public init(data: [Entry], summary: Summary?) {\n        self.data = data\n        self.summary = summary\n    }\n}\n\npublic struct CostUsageSessionReport: Sendable, Decodable {\n    public struct Entry: Sendable, Decodable, Equatable {\n        public let session: String\n        public let inputTokens: Int?\n        public let outputTokens: Int?\n        public let totalTokens: Int?\n        public let costUSD: Double?\n        public let lastActivity: String?\n\n        private enum CodingKeys: String, CodingKey {\n            case session\n            case sessionId\n            case inputTokens\n            case outputTokens\n            case totalTokens\n            case costUSD\n            case totalCost\n            case lastActivity\n        }\n\n        public init(from decoder: Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            self.session =\n                try container.decodeIfPresent(String.self, forKey: .session)\n                ?? container.decode(String.self, forKey: .sessionId)\n            self.inputTokens = try container.decodeIfPresent(Int.self, forKey: .inputTokens)\n            self.outputTokens = try container.decodeIfPresent(Int.self, forKey: .outputTokens)\n            self.totalTokens = try container.decodeIfPresent(Int.self, forKey: .totalTokens)\n            self.costUSD =\n                try container.decodeIfPresent(Double.self, forKey: .costUSD)\n                ?? container.decodeIfPresent(Double.self, forKey: .totalCost)\n            self.lastActivity = try container.decodeIfPresent(String.self, forKey: .lastActivity)\n        }\n    }\n\n    public struct Summary: Sendable, Decodable, Equatable {\n        public let totalCostUSD: Double?\n\n        private enum CodingKeys: String, CodingKey {\n            case totalCostUSD\n            case totalCost\n        }\n\n        public init(from decoder: Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            self.totalCostUSD =\n                try container.decodeIfPresent(Double.self, forKey: .totalCostUSD)\n                ?? container.decodeIfPresent(Double.self, forKey: .totalCost)\n        }\n    }\n\n    public let data: [Entry]\n    public let summary: Summary?\n\n    private enum CodingKeys: String, CodingKey {\n        case type\n        case data\n        case summary\n        case sessions\n        case totals\n    }\n\n    public init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        if container.contains(.type) {\n            _ = try container.decode(String.self, forKey: .type)\n            self.data = try container.decode([Entry].self, forKey: .data)\n            self.summary = try container.decodeIfPresent(Summary.self, forKey: .summary)\n            return\n        }\n\n        self.data = try container.decode([Entry].self, forKey: .sessions)\n        self.summary = try container.decodeIfPresent(Summary.self, forKey: .totals)\n    }\n}\n\npublic struct CostUsageMonthlyReport: Sendable, Decodable {\n    public struct Entry: Sendable, Decodable, Equatable {\n        public let month: String\n        public let totalTokens: Int?\n        public let costUSD: Double?\n\n        private enum CodingKeys: String, CodingKey {\n            case month\n            case totalTokens\n            case costUSD\n            case totalCost\n        }\n\n        public init(from decoder: Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            self.month = try container.decode(String.self, forKey: .month)\n            self.totalTokens = try container.decodeIfPresent(Int.self, forKey: .totalTokens)\n            self.costUSD =\n                try container.decodeIfPresent(Double.self, forKey: .costUSD)\n                ?? container.decodeIfPresent(Double.self, forKey: .totalCost)\n        }\n    }\n\n    public struct Summary: Sendable, Decodable, Equatable {\n        public let totalTokens: Int?\n        public let totalCostUSD: Double?\n\n        private enum CodingKeys: String, CodingKey {\n            case totalTokens\n            case costUSD\n            case totalCostUSD\n            case totalCost\n        }\n\n        public init(from decoder: Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            self.totalTokens = try container.decodeIfPresent(Int.self, forKey: .totalTokens)\n            self.totalCostUSD =\n                try container.decodeIfPresent(Double.self, forKey: .totalCostUSD)\n                ?? container.decodeIfPresent(Double.self, forKey: .costUSD)\n                ?? container.decodeIfPresent(Double.self, forKey: .totalCost)\n        }\n    }\n\n    public let data: [Entry]\n    public let summary: Summary?\n\n    private enum CodingKeys: String, CodingKey {\n        case type\n        case data\n        case summary\n        case monthly\n        case totals\n    }\n\n    public init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n\n        if container.contains(.type) {\n            _ = try container.decode(String.self, forKey: .type)\n            self.data = try container.decode([Entry].self, forKey: .data)\n            self.summary = try container.decodeIfPresent(Summary.self, forKey: .summary)\n            return\n        }\n\n        self.data = try container.decode([Entry].self, forKey: .monthly)\n        self.summary = try container.decodeIfPresent(Summary.self, forKey: .totals)\n    }\n}\n\nprivate struct CostUsageLegacyTotals: Decodable {\n    let totalInputTokens: Int?\n    let totalOutputTokens: Int?\n    let cacheReadTokens: Int?\n    let cacheCreationTokens: Int?\n    let totalTokens: Int?\n    let totalCost: Double?\n\n    private enum CodingKeys: String, CodingKey {\n        case totalInputTokens\n        case totalOutputTokens\n        case cacheReadTokens\n        case cacheCreationTokens\n        case totalCacheReadTokens\n        case totalCacheCreationTokens\n        case totalTokens\n        case totalCost\n    }\n\n    init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        self.totalInputTokens = try container.decodeIfPresent(Int.self, forKey: .totalInputTokens)\n        self.totalOutputTokens = try container.decodeIfPresent(Int.self, forKey: .totalOutputTokens)\n        self.cacheReadTokens =\n            try container.decodeIfPresent(Int.self, forKey: .cacheReadTokens)\n            ?? container.decodeIfPresent(Int.self, forKey: .totalCacheReadTokens)\n        self.cacheCreationTokens =\n            try container.decodeIfPresent(Int.self, forKey: .cacheCreationTokens)\n            ?? container.decodeIfPresent(Int.self, forKey: .totalCacheCreationTokens)\n        self.totalTokens = try container.decodeIfPresent(Int.self, forKey: .totalTokens)\n        self.totalCost = try container.decodeIfPresent(Double.self, forKey: .totalCost)\n    }\n}\n\nprivate struct CostUsageAnyCodingKey: CodingKey {\n    var intValue: Int?\n    var stringValue: String\n\n    init?(intValue: Int) {\n        self.intValue = intValue\n        self.stringValue = \"\\(intValue)\"\n    }\n\n    init?(stringValue: String) {\n        self.stringValue = stringValue\n        self.intValue = nil\n    }\n}\n\nenum CostUsageDateParser {\n    static func parse(_ text: String?) -> Date? {\n        guard let text, !text.isEmpty else { return nil }\n        let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)\n\n        let iso = ISO8601DateFormatter()\n        iso.formatOptions = [.withInternetDateTime, .withFractionalSeconds]\n        if let d = iso.date(from: trimmed) { return d }\n        iso.formatOptions = [.withInternetDateTime]\n        if let d = iso.date(from: trimmed) { return d }\n\n        let day = DateFormatter()\n        day.locale = Locale(identifier: \"en_US_POSIX\")\n        day.timeZone = TimeZone.current\n        day.dateFormat = \"yyyy-MM-dd\"\n        if let d = day.date(from: trimmed) { return d }\n\n        let monthDayYear = DateFormatter()\n        monthDayYear.locale = Locale(identifier: \"en_US_POSIX\")\n        monthDayYear.timeZone = TimeZone.current\n        monthDayYear.dateFormat = \"MMM d, yyyy\"\n        if let d = monthDayYear.date(from: trimmed) { return d }\n\n        return nil\n    }\n\n    static func parseMonth(_ text: String?) -> Date? {\n        guard let text, !text.isEmpty else { return nil }\n        let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)\n\n        let monthYear = DateFormatter()\n        monthYear.locale = Locale(identifier: \"en_US_POSIX\")\n        monthYear.timeZone = TimeZone.current\n        monthYear.dateFormat = \"MMM yyyy\"\n        if let d = monthYear.date(from: trimmed) { return d }\n\n        let fullMonthYear = DateFormatter()\n        fullMonthYear.locale = Locale(identifier: \"en_US_POSIX\")\n        fullMonthYear.timeZone = TimeZone.current\n        fullMonthYear.dateFormat = \"MMMM yyyy\"\n        if let d = fullMonthYear.date(from: trimmed) { return d }\n\n        let ym = DateFormatter()\n        ym.locale = Locale(identifier: \"en_US_POSIX\")\n        ym.timeZone = TimeZone.current\n        ym.dateFormat = \"yyyy-MM\"\n        if let d = ym.date(from: trimmed) { return d }\n\n        return nil\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/CreditsModels.swift",
    "content": "import Foundation\n\npublic struct CreditEvent: Identifiable, Equatable, Codable, Sendable {\n    public var id: UUID\n    public let date: Date\n    public let service: String\n    public let creditsUsed: Double\n\n    public init(id: UUID = UUID(), date: Date, service: String, creditsUsed: Double) {\n        self.id = id\n        self.date = date\n        self.service = service\n        self.creditsUsed = creditsUsed\n    }\n\n    private enum CodingKeys: String, CodingKey {\n        case id\n        case date\n        case service\n        case creditsUsed\n    }\n\n    public init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        self.id = try container.decodeIfPresent(UUID.self, forKey: .id) ?? UUID()\n        self.date = try container.decode(Date.self, forKey: .date)\n        self.service = try container.decode(String.self, forKey: .service)\n        self.creditsUsed = try container.decode(Double.self, forKey: .creditsUsed)\n    }\n\n    public func encode(to encoder: Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        try container.encode(self.id, forKey: .id)\n        try container.encode(self.date, forKey: .date)\n        try container.encode(self.service, forKey: .service)\n        try container.encode(self.creditsUsed, forKey: .creditsUsed)\n    }\n}\n\npublic struct CreditsSnapshot: Equatable, Codable, Sendable {\n    public let remaining: Double\n    public let events: [CreditEvent]\n    public let updatedAt: Date\n\n    public init(remaining: Double, events: [CreditEvent], updatedAt: Date) {\n        self.remaining = remaining\n        self.events = events\n        self.updatedAt = updatedAt\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Double+Clamped.swift",
    "content": "extension Double {\n    public func clamped(to range: ClosedRange<Double>) -> Double {\n        min(range.upperBound, max(range.lowerBound, self))\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Host/PTY/TTYCommandRunner.swift",
    "content": "#if canImport(Darwin)\nimport Darwin\n#else\nimport Glibc\n#endif\nimport Foundation\n\nprivate enum TTYCommandRunnerActiveProcessRegistry {\n    private static let lock = NSLock()\n    private nonisolated(unsafe) static var processes: [pid_t: ProcessInfo] = [:]\n    private nonisolated(unsafe) static var isShuttingDown = false\n\n    private struct ProcessInfo {\n        let binary: String\n        var processGroup: pid_t?\n    }\n\n    @discardableResult\n    static func register(pid: pid_t, binary: String) -> Bool {\n        guard pid > 0 else { return false }\n        self.lock.lock()\n        defer { self.lock.unlock() }\n        guard !self.isShuttingDown else { return false }\n        self.processes[pid] = ProcessInfo(binary: binary, processGroup: nil)\n        return true\n    }\n\n    static func updateProcessGroup(pid: pid_t, processGroup: pid_t?) {\n        guard pid > 0 else { return }\n        self.lock.lock()\n        guard var existing = self.processes[pid] else {\n            self.lock.unlock()\n            return\n        }\n        existing.processGroup = processGroup\n        self.processes[pid] = existing\n        self.lock.unlock()\n    }\n\n    static func unregister(pid: pid_t) {\n        guard pid > 0 else { return }\n        self.lock.lock()\n        self.processes.removeValue(forKey: pid)\n        self.lock.unlock()\n    }\n\n    static func drainForShutdown() -> [(pid: pid_t, binary: String, processGroup: pid_t?)] {\n        self.lock.lock()\n        self.isShuttingDown = true\n        let drained = self.processes.map {\n            (pid: $0.key, binary: $0.value.binary, processGroup: $0.value.processGroup)\n        }\n        self.processes.removeAll()\n        self.lock.unlock()\n        return drained\n    }\n\n    static func reset() {\n        self.lock.lock()\n        self.processes.removeAll()\n        self.isShuttingDown = false\n        self.lock.unlock()\n    }\n\n    static func count() -> Int {\n        self.lock.lock()\n        let count = self.processes.count\n        self.lock.unlock()\n        return count\n    }\n\n    static func testTrackProcess(pid: pid_t, binary: String, processGroup: pid_t?) {\n        guard pid > 0 else { return }\n        self.lock.lock()\n        self.processes[pid] = ProcessInfo(binary: binary, processGroup: processGroup)\n        self.lock.unlock()\n    }\n}\n\n/// Executes an interactive CLI inside a pseudo-terminal and returns all captured text.\n/// Keeps it minimal so we can reuse for Codex and Claude without tmux.\npublic struct TTYCommandRunner {\n    private static let log = CodexBarLog.logger(LogCategories.ttyRunner)\n\n    public struct Result: Sendable {\n        public let text: String\n    }\n\n    public struct Options: Sendable {\n        public var rows: UInt16 = 50\n        public var cols: UInt16 = 160\n        public var timeout: TimeInterval = 20.0\n        /// Stop early once output has been idle for this long (only for non-Codex flows).\n        /// Useful for interactive TUIs that render once and then wait for input indefinitely.\n        public var idleTimeout: TimeInterval?\n        public var workingDirectory: URL?\n        public var extraArgs: [String] = []\n        public var initialDelay: TimeInterval = 0.4\n        public var sendEnterEvery: TimeInterval?\n        public var sendOnSubstrings: [String: String]\n        public var stopOnURL: Bool\n        public var stopOnSubstrings: [String]\n        public var settleAfterStop: TimeInterval\n\n        public init(\n            rows: UInt16 = 50,\n            cols: UInt16 = 160,\n            timeout: TimeInterval = 20.0,\n            idleTimeout: TimeInterval? = nil,\n            workingDirectory: URL? = nil,\n            extraArgs: [String] = [],\n            initialDelay: TimeInterval = 0.4,\n            sendEnterEvery: TimeInterval? = nil,\n            sendOnSubstrings: [String: String] = [:],\n            stopOnURL: Bool = false,\n            stopOnSubstrings: [String] = [],\n            settleAfterStop: TimeInterval = 0.25)\n        {\n            self.rows = rows\n            self.cols = cols\n            self.timeout = timeout\n            self.idleTimeout = idleTimeout\n            self.workingDirectory = workingDirectory\n            self.extraArgs = extraArgs\n            self.initialDelay = initialDelay\n            self.sendEnterEvery = sendEnterEvery\n            self.sendOnSubstrings = sendOnSubstrings\n            self.stopOnURL = stopOnURL\n            self.stopOnSubstrings = stopOnSubstrings\n            self.settleAfterStop = settleAfterStop\n        }\n    }\n\n    public enum Error: Swift.Error, LocalizedError, Sendable {\n        case binaryNotFound(String)\n        case launchFailed(String)\n        case timedOut\n\n        public var errorDescription: String? {\n            switch self {\n            case let .binaryNotFound(bin):\n                \"Missing CLI '\\(bin)'. Install it (e.g. npm i -g @openai/codex) or add it to PATH.\"\n            case let .launchFailed(msg): \"Failed to launch process: \\(msg)\"\n            case .timedOut: \"PTY command timed out.\"\n            }\n        }\n    }\n\n    public init() {}\n\n    public static func terminateActiveProcessesForAppShutdown() {\n        let targets = TTYCommandRunnerActiveProcessRegistry.drainForShutdown()\n        guard !targets.isEmpty else { return }\n\n        let resolvedTargets = self.resolveShutdownTargets(\n            targets,\n            hostProcessGroup: getpgrp(),\n            groupResolver: { getpgid($0) })\n\n        for target in resolvedTargets where target.pid > 0 {\n            if let pgid = target.processGroup {\n                kill(-pgid, SIGTERM)\n            }\n            kill(target.pid, SIGTERM)\n        }\n\n        for target in resolvedTargets where target.pid > 0 {\n            if let pgid = target.processGroup {\n                kill(-pgid, SIGKILL)\n            }\n            kill(target.pid, SIGKILL)\n        }\n    }\n\n    private static func resolveShutdownTargets(\n        _ targets: [(pid: pid_t, binary: String, processGroup: pid_t?)],\n        hostProcessGroup: pid_t,\n        groupResolver: (pid_t) -> pid_t) -> [(pid: pid_t, binary: String, processGroup: pid_t?)]\n    {\n        var resolvedTargets: [(pid: pid_t, binary: String, processGroup: pid_t?)] = []\n        resolvedTargets.reserveCapacity(targets.count)\n\n        for target in targets {\n            var resolvedGroup = target.processGroup\n            if resolvedGroup == nil {\n                let pgid = groupResolver(target.pid)\n                if pgid > 0, pgid != hostProcessGroup {\n                    resolvedGroup = pgid\n                }\n            } else if resolvedGroup == hostProcessGroup {\n                resolvedGroup = nil\n            }\n\n            resolvedTargets.append((pid: target.pid, binary: target.binary, processGroup: resolvedGroup))\n        }\n        return resolvedTargets\n    }\n\n    struct RollingBuffer {\n        private let maxNeedle: Int\n        private var tail = Data()\n\n        init(maxNeedle: Int) {\n            self.maxNeedle = max(0, maxNeedle)\n        }\n\n        mutating func append(_ data: Data) -> Data {\n            guard !data.isEmpty else { return Data() }\n\n            var combined = Data()\n            combined.reserveCapacity(self.tail.count + data.count)\n            combined.append(self.tail)\n            combined.append(data)\n\n            if self.maxNeedle > 1 {\n                if combined.count >= self.maxNeedle - 1 {\n                    self.tail = combined.suffix(self.maxNeedle - 1)\n                } else {\n                    self.tail = combined\n                }\n            } else {\n                self.tail.removeAll(keepingCapacity: true)\n            }\n\n            return combined\n        }\n\n        mutating func reset() {\n            self.tail.removeAll(keepingCapacity: true)\n        }\n    }\n\n    static func lowercasedASCII(_ data: Data) -> Data {\n        guard !data.isEmpty else { return data }\n        var out = Data(count: data.count)\n        out.withUnsafeMutableBytes { dest in\n            data.withUnsafeBytes { source in\n                let src = source.bindMemory(to: UInt8.self)\n                let dst = dest.bindMemory(to: UInt8.self)\n                for idx in 0..<src.count {\n                    var byte = src[idx]\n                    if byte >= 65, byte <= 90 { byte += 32 }\n                    dst[idx] = byte\n                }\n            }\n        }\n        return out\n    }\n\n    static func locateBundledHelper(_ name: String) -> String? {\n        let fm = FileManager.default\n\n        func isExecutable(_ path: String) -> Bool {\n            fm.isExecutableFile(atPath: path)\n        }\n\n        if let override = ProcessInfo.processInfo.environment[\"CODEXBAR_HELPER_\\(name.uppercased())\"],\n           isExecutable(override)\n        {\n            return override\n        }\n\n        func candidate(inAppBundleURL appURL: URL) -> String? {\n            let path = appURL\n                .appendingPathComponent(\"Contents\", isDirectory: true)\n                .appendingPathComponent(\"Helpers\", isDirectory: true)\n                .appendingPathComponent(name, isDirectory: false)\n                .path\n            return isExecutable(path) ? path : nil\n        }\n\n        let mainURL = Bundle.main.bundleURL\n        if mainURL.pathExtension == \"app\", let found = candidate(inAppBundleURL: mainURL) { return found }\n\n        if let argv0 = CommandLine.arguments.first {\n            var url = URL(fileURLWithPath: argv0)\n            if !argv0.hasPrefix(\"/\") {\n                url = URL(fileURLWithPath: FileManager.default.currentDirectoryPath).appendingPathComponent(argv0)\n            }\n            var probe = url\n            for _ in 0..<6 {\n                let parent = probe.deletingLastPathComponent()\n                if parent.pathExtension == \"app\", let found = candidate(inAppBundleURL: parent) { return found }\n                if parent.path == probe.path { break }\n                probe = parent\n            }\n        }\n\n        return nil\n    }\n\n    // swiftlint:disable function_body_length\n    // swiftlint:disable:next cyclomatic_complexity\n    public func run(\n        binary: String,\n        send script: String,\n        options: Options = Options(),\n        onURLDetected: (@Sendable () -> Void)? = nil) throws -> Result\n    {\n        let resolved: String\n        if FileManager.default.isExecutableFile(atPath: binary) {\n            resolved = binary\n        } else if let hit = Self.which(binary) {\n            resolved = hit\n        } else {\n            Self.log.warning(\"PTY binary not found\", metadata: [\"binary\": binary])\n            throw Error.binaryNotFound(binary)\n        }\n\n        let binaryName = URL(fileURLWithPath: resolved).lastPathComponent\n        Self.log.debug(\n            \"PTY start\",\n            metadata: [\n                \"binary\": binaryName,\n                \"timeout\": \"\\(options.timeout)\",\n                \"rows\": \"\\(options.rows)\",\n                \"cols\": \"\\(options.cols)\",\n                \"args\": \"\\(options.extraArgs.count)\",\n            ])\n\n        var primaryFD: Int32 = -1\n        var secondaryFD: Int32 = -1\n        var win = winsize(ws_row: options.rows, ws_col: options.cols, ws_xpixel: 0, ws_ypixel: 0)\n        guard openpty(&primaryFD, &secondaryFD, nil, nil, &win) == 0 else {\n            Self.log.warning(\"PTY openpty failed\", metadata: [\"binary\": binaryName])\n            throw Error.launchFailed(\"openpty failed\")\n        }\n        // Make primary side non-blocking so read loops don't hang when no data is available.\n        _ = fcntl(primaryFD, F_SETFL, O_NONBLOCK)\n\n        let primaryHandle = FileHandle(fileDescriptor: primaryFD, closeOnDealloc: true)\n        let secondaryHandle = FileHandle(fileDescriptor: secondaryFD, closeOnDealloc: true)\n\n        func writeAllToPrimary(_ data: Data) throws {\n            try data.withUnsafeBytes { rawBytes in\n                guard let baseAddress = rawBytes.baseAddress else { return }\n                var offset = 0\n                var retries = 0\n                while offset < rawBytes.count {\n                    let written = write(primaryFD, baseAddress.advanced(by: offset), rawBytes.count - offset)\n                    if written > 0 {\n                        offset += written\n                        retries = 0\n                        continue\n                    }\n                    if written == 0 { break }\n\n                    let err = errno\n                    if err == EAGAIN || err == EWOULDBLOCK {\n                        retries += 1\n                        if retries > 200 {\n                            throw Error.launchFailed(\"write to PTY would block\")\n                        }\n                        usleep(5000)\n                        continue\n                    }\n                    throw Error.launchFailed(\"write to PTY failed: \\(String(cString: strerror(err)))\")\n                }\n            }\n        }\n\n        let proc = Process()\n        let resolvedURL = URL(fileURLWithPath: resolved)\n        if resolvedURL.lastPathComponent == \"claude\",\n           let watchdog = Self.locateBundledHelper(\"CodexBarClaudeWatchdog\")\n        {\n            proc.executableURL = URL(fileURLWithPath: watchdog)\n            proc.arguments = [\"--\", resolved] + options.extraArgs\n        } else {\n            proc.executableURL = resolvedURL\n            proc.arguments = options.extraArgs\n        }\n        proc.standardInput = secondaryHandle\n        proc.standardOutput = secondaryHandle\n        proc.standardError = secondaryHandle\n        // Use login-shell PATH when available, but keep the caller’s environment (HOME, LANG, etc.) so\n        // the CLIs can find their auth/config files.\n        var env = Self.enrichedEnvironment()\n        if let workingDirectory = options.workingDirectory {\n            proc.currentDirectoryURL = workingDirectory\n            env[\"PWD\"] = workingDirectory.path\n        }\n        proc.environment = env\n\n        var cleanedUp = false\n        var didLaunch = false\n        var processGroup: pid_t?\n        /// Always tear down the PTY child (and its process group) even if we throw early\n        /// while bootstrapping the CLI (e.g. when it prompts for login/telemetry).\n        func cleanup() {\n            guard !cleanedUp else { return }\n\n            if didLaunch, proc.isRunning {\n                Self.log.debug(\"PTY stopping\", metadata: [\"binary\": binaryName])\n                let exitData = Data(\"/exit\\n\".utf8)\n                try? writeAllToPrimary(exitData)\n            }\n\n            try? primaryHandle.close()\n            try? secondaryHandle.close()\n\n            guard didLaunch else { return }\n\n            if proc.isRunning {\n                proc.terminate()\n            }\n            if let pgid = processGroup {\n                kill(-pgid, SIGTERM)\n            }\n            let waitDeadline = Date().addingTimeInterval(2.0)\n            while proc.isRunning, Date() < waitDeadline {\n                usleep(100_000)\n            }\n            if proc.isRunning {\n                if let pgid = processGroup {\n                    kill(-pgid, SIGKILL)\n                }\n                kill(proc.processIdentifier, SIGKILL)\n            }\n            if didLaunch {\n                proc.waitUntilExit()\n            }\n\n            cleanedUp = true\n            if didLaunch {\n                TTYCommandRunnerActiveProcessRegistry.unregister(pid: proc.processIdentifier)\n            }\n        }\n\n        // Ensure the PTY process is always torn down, even when we throw early (e.g. login prompt).\n        defer { cleanup() }\n\n        do {\n            try proc.run()\n            didLaunch = true\n        } catch {\n            Self.log.warning(\n                \"PTY launch failed\",\n                metadata: [\"binary\": binaryName, \"error\": error.localizedDescription])\n            throw Error.launchFailed(error.localizedDescription)\n        }\n\n        // Isolate early so deferred cleanup can still terminate the whole subtree even if\n        // registration is rejected because app shutdown has started.\n        let pid = proc.processIdentifier\n        if setpgid(pid, pid) == 0 {\n            processGroup = pid\n        }\n\n        guard TTYCommandRunnerActiveProcessRegistry.register(pid: pid, binary: binaryName) else {\n            Self.log.debug(\"PTY launch blocked by shutdown fence\", metadata: [\"binary\": binaryName])\n            throw Error.launchFailed(\"App shutdown in progress\")\n        }\n        if let processGroup {\n            TTYCommandRunnerActiveProcessRegistry.updateProcessGroup(pid: pid, processGroup: processGroup)\n        }\n        Self.log.debug(\"PTY launched\", metadata: [\"binary\": binaryName])\n\n        func send(_ text: String) throws {\n            guard let data = text.data(using: .utf8) else { return }\n            try writeAllToPrimary(data)\n        }\n\n        let deadline = Date().addingTimeInterval(options.timeout)\n        let trimmed = script.trimmingCharacters(in: .whitespacesAndNewlines)\n        let isCodex = (binaryName == \"codex\")\n        let isCodexStatus = isCodex && trimmed == \"/status\"\n\n        var buffer = Data()\n        func readChunk() -> Data {\n            var appended = Data()\n            while true {\n                var tmp = [UInt8](repeating: 0, count: 8192)\n                let n = read(primaryFD, &tmp, tmp.count)\n                if n > 0 {\n                    let slice = tmp.prefix(n)\n                    buffer.append(contentsOf: slice)\n                    appended.append(contentsOf: slice)\n                    continue\n                }\n                break\n            }\n            return appended\n        }\n\n        func firstLink(in data: Data) -> String? {\n            guard let s = String(data: data, encoding: .utf8) else { return nil }\n            let pattern = #\"https?://[A-Za-z0-9._~:/?#\\[\\]@!$&'()*+,;=%-]+\"#\n            guard let regex = try? NSRegularExpression(pattern: pattern) else { return nil }\n            let range = NSRange(s.startIndex..<s.endIndex, in: s)\n            guard let match = regex.firstMatch(in: s, range: range), let r = Range(match.range, in: s) else {\n                return nil\n            }\n            var url = String(s[r])\n            while let last = url.unicodeScalars.last,\n                  CharacterSet(charactersIn: \".,;:)]}>\\\"'\").contains(last)\n            {\n                url.unicodeScalars.removeLast()\n            }\n            return url\n        }\n\n        let cursorQuery = Data([0x1B, 0x5B, 0x36, 0x6E])\n\n        usleep(UInt32(options.initialDelay * 1_000_000))\n\n        // Generic path for non-Codex (e.g. Claude /login)\n        if !isCodex {\n            if !trimmed.isEmpty {\n                try send(trimmed)\n                try send(\"\\r\")\n            }\n\n            let stopNeedles = options.stopOnSubstrings.map { Data($0.utf8) }\n            let sendNeedles = options.sendOnSubstrings.map { (\n                needle: Data($0.key.utf8),\n                needleString: $0.key,\n                keys: Data($0.value.utf8)) }\n            let urlNeedles = [Data(\"https://\".utf8), Data(\"http://\".utf8)]\n            let needleLengths =\n                stopNeedles.map(\\.count) +\n                sendNeedles.map(\\.needle.count) +\n                urlNeedles.map(\\.count) +\n                [cursorQuery.count]\n            let maxNeedle = needleLengths.max() ?? cursorQuery.count\n            var scanBuffer = RollingBuffer(maxNeedle: maxNeedle)\n            var nextCursorCheckAt = Date(timeIntervalSince1970: 0)\n            var lastEnter = Date()\n            var stoppedEarly = false\n            var urlSeen = false\n            var triggeredSends = Set<Data>()\n            var recentText = \"\"\n            var lastOutputAt = Date()\n\n            while Date() < deadline {\n                let newData = readChunk()\n                if !newData.isEmpty {\n                    lastOutputAt = Date()\n                    if let chunkText = String(bytes: newData, encoding: .utf8) {\n                        recentText += chunkText\n                        if recentText.count > 8192 {\n                            recentText.removeFirst(recentText.count - 8192)\n                        }\n                    }\n                }\n                let scanData = scanBuffer.append(newData)\n                if Date() >= nextCursorCheckAt,\n                   !scanData.isEmpty,\n                   scanData.range(of: cursorQuery) != nil\n                {\n                    try? send(\"\\u{1b}[1;1R\")\n                    nextCursorCheckAt = Date().addingTimeInterval(1.0)\n                }\n\n                if !sendNeedles.isEmpty {\n                    let recentTextCollapsed = recentText.replacingOccurrences(of: \"\\r\", with: \"\")\n                    for item in sendNeedles where !triggeredSends.contains(item.needle) {\n                        let matched = scanData.range(of: item.needle) != nil ||\n                            recentText.contains(item.needleString) ||\n                            recentTextCollapsed.contains(item.needleString)\n                        if matched {\n                            if let keysString = String(data: item.keys, encoding: .utf8) {\n                                try? send(keysString)\n                            } else {\n                                try? writeAllToPrimary(item.keys)\n                            }\n                            triggeredSends.insert(item.needle)\n                        }\n                    }\n                }\n\n                if urlNeedles.contains(where: { scanData.range(of: $0) != nil }) {\n                    urlSeen = true\n                    if urlSeen {\n                        onURLDetected?()\n                    }\n                    if options.stopOnURL {\n                        stoppedEarly = true\n                        break\n                    }\n                }\n                if !stopNeedles.isEmpty, stopNeedles.contains(where: { scanData.range(of: $0) != nil }) {\n                    stoppedEarly = true\n                    break\n                }\n                if let idleTimeout = options.idleTimeout,\n                   !buffer.isEmpty,\n                   Date().timeIntervalSince(lastOutputAt) >= idleTimeout\n                {\n                    stoppedEarly = true\n                    break\n                }\n\n                if !urlSeen, let every = options.sendEnterEvery, Date().timeIntervalSince(lastEnter) >= every {\n                    try? send(\"\\r\")\n                    lastEnter = Date()\n                }\n\n                if !proc.isRunning { break }\n                usleep(60000)\n            }\n\n            if stoppedEarly {\n                let settle = max(0, min(options.settleAfterStop, deadline.timeIntervalSinceNow))\n                if settle > 0 {\n                    let settleDeadline = Date().addingTimeInterval(settle)\n                    while Date() < settleDeadline {\n                        let newData = readChunk()\n                        let scanData = scanBuffer.append(newData)\n                        if Date() >= nextCursorCheckAt,\n                           !scanData.isEmpty,\n                           scanData.range(of: cursorQuery) != nil\n                        {\n                            try? send(\"\\u{1b}[1;1R\")\n                            nextCursorCheckAt = Date().addingTimeInterval(1.0)\n                        }\n                        usleep(50000)\n                    }\n                }\n            }\n\n            let text = String(data: buffer, encoding: .utf8) ?? \"\"\n            guard !text.isEmpty else { throw Error.timedOut }\n            return Result(text: text)\n        }\n\n        // Codex-specific behavior (/status and update handling)\n        let delayInitialSend = isCodexStatus\n        if !delayInitialSend {\n            try send(script)\n            try send(\"\\r\")\n            usleep(150_000)\n            try send(\"\\r\")\n            try send(\"\\u{1b}\")\n        }\n\n        var skippedCodexUpdate = false\n        var sentScript = !delayInitialSend\n        var updateSkipAttempts = 0\n        var lastEnter = Date(timeIntervalSince1970: 0)\n        var scriptSentAt: Date? = sentScript ? Date() : nil\n        var resendStatusRetries = 0\n        var enterRetries = 0\n        var sawCodexStatus = false\n        var sawCodexUpdatePrompt = false\n        let statusMarkers = [\n            \"Credits:\",\n            \"5h limit\",\n            \"5-hour limit\",\n            \"Weekly limit\",\n        ].map { Data($0.utf8) }\n        let updateNeedles = [\"Update available!\", \"Run bun install -g @openai/codex\", \"0.60.1 ->\"]\n        let updateNeedlesLower = updateNeedles.map { Data($0.lowercased().utf8) }\n        let statusNeedleLengths = statusMarkers.map(\\.count)\n        let updateNeedleLengths = updateNeedlesLower.map(\\.count)\n        let statusMaxNeedle = ([cursorQuery.count] + statusNeedleLengths).max() ?? cursorQuery.count\n        let updateMaxNeedle = updateNeedleLengths.max() ?? 0\n        var statusScanBuffer = RollingBuffer(maxNeedle: statusMaxNeedle)\n        var updateScanBuffer = RollingBuffer(maxNeedle: updateMaxNeedle)\n        var nextCursorCheckAt = Date(timeIntervalSince1970: 0)\n\n        while Date() < deadline {\n            let newData = readChunk()\n            let scanData = statusScanBuffer.append(newData)\n            if Date() >= nextCursorCheckAt,\n               !scanData.isEmpty,\n               scanData.range(of: cursorQuery) != nil\n            {\n                try? send(\"\\u{1b}[1;1R\")\n                nextCursorCheckAt = Date().addingTimeInterval(1.0)\n            }\n            if !scanData.isEmpty, !sawCodexStatus {\n                if statusMarkers.contains(where: { scanData.range(of: $0) != nil }) {\n                    sawCodexStatus = true\n                }\n            }\n\n            if !skippedCodexUpdate, !sawCodexUpdatePrompt, !newData.isEmpty {\n                let lowerData = Self.lowercasedASCII(newData)\n                let lowerScan = updateScanBuffer.append(lowerData)\n                if !sawCodexUpdatePrompt {\n                    if updateNeedlesLower.contains(where: { lowerScan.range(of: $0) != nil }) {\n                        sawCodexUpdatePrompt = true\n                    }\n                }\n            }\n\n            if !skippedCodexUpdate, sawCodexUpdatePrompt {\n                // Prompt shows options: 1) Update now, 2) Skip, 3) Skip until next version.\n                // Users report one Down + Enter is enough; follow with an extra Enter for safety, then re-run\n                // /status.\n                try? send(\"\\u{1b}[B\") // highlight option 2 (Skip)\n                usleep(120_000)\n                try? send(\"\\r\")\n                usleep(150_000)\n                try? send(\"\\r\") // if still focused on prompt, confirm again\n                try? send(\"/status\")\n                try? send(\"\\r\")\n                updateSkipAttempts += 1\n                if updateSkipAttempts >= 1 {\n                    skippedCodexUpdate = true\n                    sentScript = false // re-send /status after dismissing\n                    scriptSentAt = nil\n                    buffer.removeAll()\n                    statusScanBuffer.reset()\n                    updateScanBuffer.reset()\n                    sawCodexStatus = false\n                }\n                usleep(300_000)\n            }\n            if !sentScript, !sawCodexUpdatePrompt || skippedCodexUpdate {\n                try? send(script)\n                try? send(\"\\r\")\n                sentScript = true\n                scriptSentAt = Date()\n                lastEnter = Date()\n                usleep(200_000)\n                continue\n            }\n            if sentScript, !sawCodexStatus {\n                if Date().timeIntervalSince(lastEnter) >= 1.2, enterRetries < 6 {\n                    try? send(\"\\r\")\n                    enterRetries += 1\n                    lastEnter = Date()\n                    usleep(120_000)\n                    continue\n                }\n                if let sentAt = scriptSentAt,\n                   Date().timeIntervalSince(sentAt) >= 3.0,\n                   resendStatusRetries < 2\n                {\n                    try? send(\"/status\")\n                    try? send(\"\\r\")\n                    resendStatusRetries += 1\n                    buffer.removeAll()\n                    statusScanBuffer.reset()\n                    updateScanBuffer.reset()\n                    sawCodexStatus = false\n                    scriptSentAt = Date()\n                    lastEnter = Date()\n                    usleep(220_000)\n                    continue\n                }\n            }\n            if sawCodexStatus { break }\n            usleep(120_000)\n        }\n\n        if sawCodexStatus {\n            let settleDeadline = Date().addingTimeInterval(2.0)\n            while Date() < settleDeadline {\n                let newData = readChunk()\n                let scanData = statusScanBuffer.append(newData)\n                if Date() >= nextCursorCheckAt,\n                   !scanData.isEmpty,\n                   scanData.range(of: cursorQuery) != nil\n                {\n                    try? send(\"\\u{1b}[1;1R\")\n                    nextCursorCheckAt = Date().addingTimeInterval(1.0)\n                }\n                usleep(100_000)\n            }\n        }\n\n        guard let text = String(data: buffer, encoding: .utf8), !text.isEmpty else {\n            throw Error.timedOut\n        }\n\n        return Result(text: text)\n    }\n\n    // swiftlint:enable function_body_length\n\n    public static func which(_ tool: String) -> String? {\n        if tool == \"codex\", let located = BinaryLocator.resolveCodexBinary() { return located }\n        if tool == \"claude\", let located = BinaryLocator.resolveClaudeBinary() { return located }\n        return self.runWhich(tool)\n    }\n\n    private static func runWhich(_ tool: String) -> String? {\n        let proc = Process()\n        proc.executableURL = URL(fileURLWithPath: \"/usr/bin/which\")\n        proc.arguments = [tool]\n        var env = ProcessInfo.processInfo.environment\n        env[\"PATH\"] = PathBuilder.effectivePATH(purposes: [.tty, .nodeTooling], env: env)\n        proc.environment = env\n        let pipe = Pipe()\n        proc.standardOutput = pipe\n        try? proc.run()\n        proc.waitUntilExit()\n        guard proc.terminationStatus == 0 else { return nil }\n        let data = pipe.fileHandleForReading.readDataToEndOfFile()\n        guard let path = String(data: data, encoding: .utf8)?\n            .trimmingCharacters(in: .whitespacesAndNewlines),\n            !path.isEmpty else { return nil }\n        return path\n    }\n\n    /// Uses login-shell PATH when available so TTY probes match the user's shell configuration.\n    public static func enrichedPath() -> String {\n        PathBuilder.effectivePATH(\n            purposes: [.tty, .nodeTooling],\n            env: ProcessInfo.processInfo.environment)\n    }\n\n    static func enrichedEnvironment(\n        baseEnv: [String: String] = ProcessInfo.processInfo.environment,\n        loginPATH: [String]? = LoginShellPathCache.shared.current,\n        home: String = NSHomeDirectory()) -> [String: String]\n    {\n        var env = baseEnv\n        env[\"PATH\"] = PathBuilder.effectivePATH(\n            purposes: [.tty, .nodeTooling],\n            env: baseEnv,\n            loginPATH: loginPATH,\n            home: home)\n        if env[\"HOME\"]?.isEmpty ?? true {\n            env[\"HOME\"] = home\n        }\n        if env[\"TERM\"]?.isEmpty ?? true {\n            env[\"TERM\"] = \"xterm-256color\"\n        }\n        if env[\"COLORTERM\"]?.isEmpty ?? true {\n            env[\"COLORTERM\"] = \"truecolor\"\n        }\n        if env[\"LANG\"]?.isEmpty ?? true {\n            env[\"LANG\"] = \"en_US.UTF-8\"\n        }\n        if env[\"CI\"] == nil {\n            env[\"CI\"] = \"0\"\n        }\n        return env\n    }\n\n    static func _test_resetTrackedProcesses() {\n        TTYCommandRunnerActiveProcessRegistry.reset()\n    }\n\n    static func _test_trackProcess(pid: pid_t, binary: String, processGroup: pid_t?) {\n        TTYCommandRunnerActiveProcessRegistry.testTrackProcess(\n            pid: pid,\n            binary: binary,\n            processGroup: processGroup)\n    }\n\n    @discardableResult\n    static func _test_registerTrackedProcess(pid: pid_t, binary: String) -> Bool {\n        TTYCommandRunnerActiveProcessRegistry.register(pid: pid, binary: binary)\n    }\n\n    static func _test_trackedProcessCount() -> Int {\n        TTYCommandRunnerActiveProcessRegistry.count()\n    }\n\n    static func _test_drainTrackedProcessesForShutdown() -> [(pid: pid_t, binary: String, processGroup: pid_t?)] {\n        TTYCommandRunnerActiveProcessRegistry.drainForShutdown()\n    }\n\n    static func _test_resolveShutdownTargets(\n        _ targets: [(pid: pid_t, binary: String, processGroup: pid_t?)],\n        hostProcessGroup: pid_t,\n        groupResolver: (pid_t) -> pid_t) -> [(pid: pid_t, binary: String, processGroup: pid_t?)]\n    {\n        self.resolveShutdownTargets(targets, hostProcessGroup: hostProcessGroup, groupResolver: groupResolver)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Host/Process/SubprocessRunner.swift",
    "content": "#if canImport(Darwin)\nimport Darwin\n#else\nimport Glibc\n#endif\nimport Foundation\n\npublic enum SubprocessRunnerError: LocalizedError, Sendable {\n    case binaryNotFound(String)\n    case launchFailed(String)\n    case timedOut(String)\n    case nonZeroExit(code: Int32, stderr: String)\n\n    public var errorDescription: String? {\n        switch self {\n        case let .binaryNotFound(binary):\n            return \"Missing CLI '\\(binary)'. Install it and restart CodexBar.\"\n        case let .launchFailed(details):\n            return \"Failed to launch process: \\(details)\"\n        case let .timedOut(label):\n            return \"Command timed out: \\(label)\"\n        case let .nonZeroExit(code, stderr):\n            let trimmed = stderr.trimmingCharacters(in: .whitespacesAndNewlines)\n            if trimmed.isEmpty {\n                return \"Command failed with exit code \\(code).\"\n            }\n            return \"Command failed (\\(code)): \\(trimmed)\"\n        }\n    }\n}\n\npublic struct SubprocessResult: Sendable {\n    public let stdout: String\n    public let stderr: String\n}\n\npublic enum SubprocessRunner {\n    private static let log = CodexBarLog.logger(LogCategories.subprocess)\n\n    public static func run(\n        binary: String,\n        arguments: [String],\n        environment: [String: String],\n        timeout: TimeInterval,\n        label: String) async throws -> SubprocessResult\n    {\n        guard FileManager.default.isExecutableFile(atPath: binary) else {\n            throw SubprocessRunnerError.binaryNotFound(binary)\n        }\n\n        let start = Date()\n        let binaryName = URL(fileURLWithPath: binary).lastPathComponent\n        self.log.debug(\n            \"Subprocess start\",\n            metadata: [\"label\": label, \"binary\": binaryName, \"timeout\": \"\\(timeout)\"])\n\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: binary)\n        process.arguments = arguments\n        process.environment = environment\n\n        let stdoutPipe = Pipe()\n        let stderrPipe = Pipe()\n        process.standardOutput = stdoutPipe\n        process.standardError = stderrPipe\n        process.standardInput = nil\n\n        let stdoutTask = Task<Data, Never> {\n            stdoutPipe.fileHandleForReading.readDataToEndOfFile()\n        }\n        let stderrTask = Task<Data, Never> {\n            stderrPipe.fileHandleForReading.readDataToEndOfFile()\n        }\n\n        do {\n            try process.run()\n        } catch {\n            stdoutTask.cancel()\n            stderrTask.cancel()\n            stdoutPipe.fileHandleForReading.closeFile()\n            stderrPipe.fileHandleForReading.closeFile()\n            throw SubprocessRunnerError.launchFailed(error.localizedDescription)\n        }\n\n        var processGroup: pid_t?\n        let pid = process.processIdentifier\n        if setpgid(pid, pid) == 0 {\n            processGroup = pid\n        }\n\n        let exitCodeTask = Task<Int32, Never> {\n            process.waitUntilExit()\n            return process.terminationStatus\n        }\n\n        do {\n            let exitCode = try await withThrowingTaskGroup(of: Int32.self) { group in\n                group.addTask { await exitCodeTask.value }\n                group.addTask {\n                    try await Task.sleep(for: .seconds(timeout))\n                    throw SubprocessRunnerError.timedOut(label)\n                }\n                let code = try await group.next()!\n                group.cancelAll()\n                return code\n            }\n\n            let stdoutData = await stdoutTask.value\n            let stderrData = await stderrTask.value\n            let stdout = String(data: stdoutData, encoding: .utf8) ?? \"\"\n            let stderr = String(data: stderrData, encoding: .utf8) ?? \"\"\n\n            if exitCode != 0 {\n                let duration = Date().timeIntervalSince(start)\n                self.log.warning(\n                    \"Subprocess failed\",\n                    metadata: [\n                        \"label\": label,\n                        \"binary\": binaryName,\n                        \"status\": \"\\(exitCode)\",\n                        \"duration_ms\": \"\\(Int(duration * 1000))\",\n                    ])\n                throw SubprocessRunnerError.nonZeroExit(code: exitCode, stderr: stderr)\n            }\n\n            let duration = Date().timeIntervalSince(start)\n            self.log.debug(\n                \"Subprocess exit\",\n                metadata: [\n                    \"label\": label,\n                    \"binary\": binaryName,\n                    \"status\": \"\\(exitCode)\",\n                    \"duration_ms\": \"\\(Int(duration * 1000))\",\n                ])\n            return SubprocessResult(stdout: stdout, stderr: stderr)\n        } catch {\n            let duration = Date().timeIntervalSince(start)\n            self.log.warning(\n                \"Subprocess error\",\n                metadata: [\n                    \"label\": label,\n                    \"binary\": binaryName,\n                    \"duration_ms\": \"\\(Int(duration * 1000))\",\n                ])\n            if process.isRunning {\n                process.terminate()\n                if let pgid = processGroup {\n                    kill(-pgid, SIGTERM)\n                }\n                let killDeadline = Date().addingTimeInterval(0.4)\n                while process.isRunning, Date() < killDeadline {\n                    usleep(50000)\n                }\n                if process.isRunning {\n                    if let pgid = processGroup {\n                        kill(-pgid, SIGKILL)\n                    }\n                    kill(process.processIdentifier, SIGKILL)\n                }\n            }\n            exitCodeTask.cancel()\n            stdoutTask.cancel()\n            stderrTask.cancel()\n            stdoutPipe.fileHandleForReading.closeFile()\n            stderrPipe.fileHandleForReading.closeFile()\n            throw error\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/KeychainAccessGate.swift",
    "content": "import Foundation\n#if canImport(SweetCookieKit)\nimport SweetCookieKit\n#endif\n\npublic enum KeychainAccessGate {\n    private static let flagKey = \"debugDisableKeychainAccess\"\n    private static let appGroupID = \"group.com.steipete.codexbar\"\n    @TaskLocal private static var taskOverrideValue: Bool?\n    private nonisolated(unsafe) static var overrideValue: Bool?\n\n    public nonisolated(unsafe) static var isDisabled: Bool {\n        get {\n            if let taskOverrideValue { return taskOverrideValue }\n            if let overrideValue { return overrideValue }\n            if UserDefaults.standard.bool(forKey: Self.flagKey) { return true }\n            if let shared = UserDefaults(suiteName: Self.appGroupID),\n               shared.bool(forKey: Self.flagKey)\n            {\n                return true\n            }\n            return false\n        }\n        set {\n            overrideValue = newValue\n            #if os(macOS) && canImport(SweetCookieKit)\n            BrowserCookieKeychainAccessGate.isDisabled = newValue\n            #endif\n        }\n    }\n\n    static func withTaskOverrideForTesting<T>(\n        _ disabled: Bool?,\n        operation: () throws -> T) rethrows -> T\n    {\n        try self.$taskOverrideValue.withValue(disabled) {\n            try operation()\n        }\n    }\n\n    static func withTaskOverrideForTesting<T>(\n        _ disabled: Bool?,\n        operation: () async throws -> T) async rethrows -> T\n    {\n        try await self.$taskOverrideValue.withValue(disabled) {\n            try await operation()\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/KeychainAccessPreflight.swift",
    "content": "#if os(macOS)\nimport LocalAuthentication\nimport Security\n#endif\n\npublic struct KeychainPromptContext: Sendable {\n    public enum Kind: Sendable {\n        case claudeOAuth\n        case codexCookie\n        case claudeCookie\n        case cursorCookie\n        case opencodeCookie\n        case factoryCookie\n        case zaiToken\n        case syntheticToken\n        case copilotToken\n        case kimiToken\n        case kimiK2Token\n        case minimaxCookie\n        case minimaxToken\n        case augmentCookie\n        case ampCookie\n    }\n\n    public let kind: Kind\n    public let service: String\n    public let account: String?\n\n    public init(kind: Kind, service: String, account: String?) {\n        self.kind = kind\n        self.service = service\n        self.account = account\n    }\n}\n\npublic enum KeychainPromptHandler {\n    final class HandlerStore: @unchecked Sendable {\n        let handler: (KeychainPromptContext) -> Void\n\n        init(handler: @escaping (KeychainPromptContext) -> Void) {\n            self.handler = handler\n        }\n    }\n\n    @TaskLocal private static var taskHandlerStore: HandlerStore?\n    public nonisolated(unsafe) static var handler: ((KeychainPromptContext) -> Void)?\n\n    public static func notify(_ context: KeychainPromptContext) {\n        if let taskHandlerStore {\n            taskHandlerStore.handler(context)\n            return\n        }\n        self.handler?(context)\n    }\n\n    #if DEBUG\n    static func withHandlerForTesting<T>(\n        _ handler: ((KeychainPromptContext) -> Void)?,\n        operation: () throws -> T) rethrows -> T\n    {\n        try self.$taskHandlerStore.withValue(handler.map(HandlerStore.init(handler:))) {\n            try operation()\n        }\n    }\n\n    static func withHandlerForTesting<T>(\n        _ handler: ((KeychainPromptContext) -> Void)?,\n        operation: () async throws -> T) async rethrows -> T\n    {\n        try await self.$taskHandlerStore.withValue(handler.map(HandlerStore.init(handler:))) {\n            try await operation()\n        }\n    }\n    #endif\n}\n\npublic enum KeychainAccessPreflight {\n    public enum Outcome: Sendable {\n        case allowed\n        case interactionRequired\n        case notFound\n        case failure(Int)\n    }\n\n    private static let log = CodexBarLog.logger(LogCategories.keychainPreflight)\n\n    #if DEBUG\n    final class CheckGenericPasswordOverrideStore: @unchecked Sendable {\n        let check: (String, String?) -> Outcome\n\n        init(check: @escaping (String, String?) -> Outcome) {\n            self.check = check\n        }\n    }\n\n    @TaskLocal private static var taskCheckGenericPasswordOverrideStore: CheckGenericPasswordOverrideStore?\n    private nonisolated(unsafe) static var checkGenericPasswordOverride: ((String, String?) -> Outcome)?\n\n    static func setCheckGenericPasswordOverrideForTesting(_ override: ((String, String?) -> Outcome)?) {\n        self.checkGenericPasswordOverride = override\n    }\n\n    static func withCheckGenericPasswordOverrideForTesting<T>(\n        _ override: ((String, String?) -> Outcome)?,\n        operation: () throws -> T) rethrows -> T\n    {\n        try self.$taskCheckGenericPasswordOverrideStore.withValue(\n            override.map(CheckGenericPasswordOverrideStore.init(check:)))\n        {\n            try operation()\n        }\n    }\n\n    static func withCheckGenericPasswordOverrideForTesting<T>(\n        _ override: ((String, String?) -> Outcome)?,\n        operation: () async throws -> T) async rethrows -> T\n    {\n        try await self.$taskCheckGenericPasswordOverrideStore.withValue(\n            override.map(CheckGenericPasswordOverrideStore.init(check:)))\n        {\n            try await operation()\n        }\n    }\n    #endif\n\n    public static func checkGenericPassword(service: String, account: String?) -> Outcome {\n        #if os(macOS)\n        #if DEBUG\n        if let override = self.taskCheckGenericPasswordOverrideStore {\n            return override.check(service, account)\n        }\n        if let override = self.checkGenericPasswordOverride {\n            return override(service, account)\n        }\n        #endif\n        guard !KeychainAccessGate.isDisabled else { return .notFound }\n        var query: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: service,\n            kSecMatchLimit as String: kSecMatchLimitOne,\n            // Preflight should never trigger UI. Avoid requesting the secret payload (`kSecReturnData`) because\n            // some macOS configurations still surface legacy prompts more aggressively when reading secret data,\n            // even with a non-interactive LAContext.\n            kSecReturnAttributes as String: true,\n        ]\n        KeychainNoUIQuery.apply(to: &query)\n        if let account {\n            query[kSecAttrAccount as String] = account\n        }\n\n        var result: AnyObject?\n        let status = SecItemCopyMatching(query as CFDictionary, &result)\n        switch status {\n        case errSecSuccess:\n            self.log.debug(\"Keychain preflight allowed\", metadata: [\"service\": service])\n            return .allowed\n        case errSecItemNotFound:\n            self.log.debug(\n                \"Keychain preflight not found\",\n                metadata: [\"service\": service])\n            return .notFound\n        case errSecInteractionNotAllowed:\n            self.log.info(\n                \"Keychain preflight requires interaction\",\n                metadata: [\"service\": service])\n            return .interactionRequired\n        default:\n            self.log.warning(\n                \"Keychain preflight failed\",\n                metadata: [\"service\": service, \"status\": \"\\(status)\"])\n            return .failure(Int(status))\n        }\n        #else\n        return .notFound\n        #endif\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/KeychainCacheStore.swift",
    "content": "import Foundation\n#if os(macOS)\nimport Security\n#endif\n\npublic enum KeychainCacheStore {\n    public struct Key: Hashable, Sendable {\n        public let category: String\n        public let identifier: String\n\n        public init(category: String, identifier: String) {\n            self.category = category\n            self.identifier = identifier\n        }\n\n        var account: String {\n            \"\\(self.category).\\(self.identifier)\"\n        }\n    }\n\n    public enum LoadResult<Entry> {\n        case found(Entry)\n        case missing\n        case invalid\n    }\n\n    private static let log = CodexBarLog.logger(LogCategories.keychainCache)\n    private static let cacheService = \"com.steipete.codexbar.cache\"\n    private static let cacheLabel = \"CodexBar Cache\"\n    private nonisolated(unsafe) static var globalServiceOverride: String?\n    @TaskLocal private static var serviceOverride: String?\n    private static let testStoreLock = NSLock()\n    private struct TestStoreKey: Hashable {\n        let service: String\n        let account: String\n    }\n\n    private nonisolated(unsafe) static var testStore: [TestStoreKey: Data]?\n    private nonisolated(unsafe) static var testStoreRefCount = 0\n\n    public static func load<Entry: Codable>(\n        key: Key,\n        as type: Entry.Type = Entry.self) -> LoadResult<Entry>\n    {\n        if let testResult = loadFromTestStore(key: key, as: type) {\n            return testResult\n        }\n        #if os(macOS)\n        let query: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: self.serviceName,\n            kSecAttrAccount as String: key.account,\n            kSecMatchLimit as String: kSecMatchLimitOne,\n            kSecReturnData as String: true,\n        ]\n\n        var result: AnyObject?\n        let status = SecItemCopyMatching(query as CFDictionary, &result)\n        switch status {\n        case errSecSuccess:\n            guard let data = result as? Data, !data.isEmpty else {\n                self.log.error(\"Keychain cache item was empty (\\(key.account))\")\n                return .invalid\n            }\n            let decoder = Self.makeDecoder()\n            guard let decoded = try? decoder.decode(Entry.self, from: data) else {\n                self.log.error(\"Failed to decode keychain cache (\\(key.account))\")\n                return .invalid\n            }\n            return .found(decoded)\n        case errSecItemNotFound:\n            return .missing\n        default:\n            self.log.error(\"Keychain cache read failed (\\(key.account)): \\(status)\")\n            return .invalid\n        }\n        #else\n        return .missing\n        #endif\n    }\n\n    public static func store(key: Key, entry: some Codable) {\n        if self.storeInTestStore(key: key, entry: entry) {\n            return\n        }\n        #if os(macOS)\n        let encoder = Self.makeEncoder()\n        guard let data = try? encoder.encode(entry) else {\n            self.log.error(\"Failed to encode keychain cache (\\(key.account))\")\n            return\n        }\n\n        let query: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: self.serviceName,\n            kSecAttrAccount as String: key.account,\n        ]\n        let updateAttrs: [String: Any] = [\n            kSecValueData as String: data,\n        ]\n\n        let updateStatus = SecItemUpdate(query as CFDictionary, updateAttrs as CFDictionary)\n        if updateStatus == errSecSuccess {\n            return\n        }\n        if updateStatus != errSecItemNotFound {\n            self.log.error(\"Keychain cache update failed (\\(key.account)): \\(updateStatus)\")\n            return\n        }\n\n        var addQuery = query\n        addQuery[kSecValueData as String] = data\n        addQuery[kSecAttrLabel as String] = self.cacheLabel\n        addQuery[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly\n\n        let addStatus = SecItemAdd(addQuery as CFDictionary, nil)\n        if addStatus != errSecSuccess {\n            self.log.error(\"Keychain cache add failed (\\(key.account)): \\(addStatus)\")\n        }\n        #endif\n    }\n\n    public static func clear(key: Key) {\n        if self.clearTestStore(key: key) {\n            return\n        }\n        #if os(macOS)\n        let query: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: self.serviceName,\n            kSecAttrAccount as String: key.account,\n        ]\n        let status = SecItemDelete(query as CFDictionary)\n        if status != errSecSuccess, status != errSecItemNotFound {\n            self.log.error(\"Keychain cache delete failed (\\(key.account)): \\(status)\")\n        }\n        #endif\n    }\n\n    static func setServiceOverrideForTesting(_ service: String?) {\n        self.globalServiceOverride = service\n    }\n\n    public static func withServiceOverrideForTesting<T>(\n        _ service: String?,\n        operation: () throws -> T) rethrows -> T\n    {\n        try self.$serviceOverride.withValue(service) {\n            try operation()\n        }\n    }\n\n    public static func withServiceOverrideForTesting<T>(\n        _ service: String?,\n        operation: () async throws -> T) async rethrows -> T\n    {\n        try await self.$serviceOverride.withValue(service) {\n            try await operation()\n        }\n    }\n\n    public static func withCurrentServiceOverrideForTesting<T>(\n        operation: () async throws -> T) async rethrows -> T\n    {\n        let service = self.serviceOverride\n        return try await self.$serviceOverride.withValue(service) {\n            try await operation()\n        }\n    }\n\n    public static var currentServiceOverrideForTesting: String? {\n        self.serviceOverride\n    }\n\n    static func setTestStoreForTesting(_ enabled: Bool) {\n        self.testStoreLock.lock()\n        defer { self.testStoreLock.unlock() }\n        if enabled {\n            self.testStoreRefCount += 1\n            if self.testStoreRefCount == 1 {\n                self.testStore = [:]\n            }\n        } else {\n            self.testStoreRefCount = max(0, self.testStoreRefCount - 1)\n            if self.testStoreRefCount == 0 {\n                self.testStore = nil\n            }\n        }\n    }\n\n    private static var serviceName: String {\n        serviceOverride ?? self.globalServiceOverride ?? self.cacheService\n    }\n\n    private static func makeEncoder() -> JSONEncoder {\n        let encoder = JSONEncoder()\n        encoder.dateEncodingStrategy = .iso8601\n        return encoder\n    }\n\n    private static func makeDecoder() -> JSONDecoder {\n        let decoder = JSONDecoder()\n        decoder.dateDecodingStrategy = .iso8601\n        return decoder\n    }\n\n    private static func loadFromTestStore<Entry: Codable>(\n        key: Key,\n        as type: Entry.Type) -> LoadResult<Entry>?\n    {\n        self.testStoreLock.lock()\n        defer { self.testStoreLock.unlock() }\n        guard let store = self.testStore else { return nil }\n        let testKey = TestStoreKey(service: self.serviceName, account: key.account)\n        guard let data = store[testKey] else { return .missing }\n        let decoder = Self.makeDecoder()\n        guard let decoded = try? decoder.decode(Entry.self, from: data) else {\n            return .invalid\n        }\n        return .found(decoded)\n    }\n\n    private static func storeInTestStore(key: Key, entry: some Codable) -> Bool {\n        self.testStoreLock.lock()\n        defer { self.testStoreLock.unlock() }\n        guard var store = self.testStore else { return false }\n        let encoder = Self.makeEncoder()\n        guard let data = try? encoder.encode(entry) else { return true }\n        let testKey = TestStoreKey(service: self.serviceName, account: key.account)\n        store[testKey] = data\n        self.testStore = store\n        return true\n    }\n\n    private static func clearTestStore(key: Key) -> Bool {\n        self.testStoreLock.lock()\n        defer { self.testStoreLock.unlock() }\n        guard var store = self.testStore else { return false }\n        let testKey = TestStoreKey(service: self.serviceName, account: key.account)\n        store.removeValue(forKey: testKey)\n        self.testStore = store\n        return true\n    }\n}\n\nextension KeychainCacheStore.Key {\n    public static func cookie(provider: UsageProvider) -> Self {\n        Self(category: \"cookie\", identifier: provider.rawValue)\n    }\n\n    public static func oauth(provider: UsageProvider) -> Self {\n        Self(category: \"oauth\", identifier: provider.rawValue)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/KeychainNoUIQuery.swift",
    "content": "import Foundation\n\n#if os(macOS)\nimport LocalAuthentication\nimport Security\n\nenum KeychainNoUIQuery {\n    static func apply(to query: inout [String: Any]) {\n        let context = LAContext()\n        context.interactionNotAllowed = true\n        query[kSecUseAuthenticationContext as String] = context\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/CodexBarCore/Logging/CodexBarLog.swift",
    "content": "import Foundation\nimport Logging\n\npublic enum CodexBarLog {\n    public enum Destination: Sendable {\n        case stderr\n        case oslog(subsystem: String)\n    }\n\n    public enum Level: String, CaseIterable, Identifiable, Sendable {\n        case trace\n        case verbose\n        case debug\n        case info\n        case warning\n        case error\n        case critical\n\n        public var id: String {\n            self.rawValue\n        }\n\n        public var displayName: String {\n            switch self {\n            case .trace: \"Trace\"\n            case .verbose: \"Verbose\"\n            case .debug: \"Debug\"\n            case .info: \"Info\"\n            case .warning: \"Warning\"\n            case .error: \"Error\"\n            case .critical: \"Critical\"\n            }\n        }\n\n        public var rank: Int {\n            switch self {\n            case .trace: 0\n            case .verbose: 1\n            case .debug: 2\n            case .info: 3\n            case .warning: 4\n            case .error: 5\n            case .critical: 6\n            }\n        }\n\n        public var asSwiftLogLevel: Logger.Level {\n            switch self {\n            case .trace: .trace\n            case .verbose: .debug\n            case .debug: .debug\n            case .info: .info\n            case .warning: .warning\n            case .error: .error\n            case .critical: .critical\n            }\n        }\n    }\n\n    public struct Configuration: Sendable {\n        public let destination: Destination\n        public let level: Level\n        public let json: Bool\n\n        public init(destination: Destination, level: Level, json: Bool) {\n            self.destination = destination\n            self.level = level\n            self.json = json\n        }\n    }\n\n    private static let lock = NSLock()\n    private static let levelLock = NSLock()\n    private nonisolated(unsafe) static var isBootstrapped = false\n    private nonisolated(unsafe) static var currentLevel: Level = .info\n\n    public static func bootstrapIfNeeded(_ config: Configuration) {\n        self.lock.lock()\n        defer { lock.unlock() }\n        guard !self.isBootstrapped else { return }\n        self.currentLevel = config.level\n\n        let baseFactory: @Sendable (String) -> any LogHandler = { label in\n            switch config.destination {\n            case .stderr:\n                if config.json { return JSONStderrLogHandler(label: label) }\n                return StreamLogHandler.standardError(label: label)\n            case let .oslog(subsystem):\n                #if canImport(os)\n                return OSLogLogHandler(label: label, subsystem: subsystem)\n                #else\n                if config.json { return JSONStderrLogHandler(label: label) }\n                return StreamLogHandler.standardError(label: label)\n                #endif\n            }\n        }\n\n        LoggingSystem.bootstrap { label in\n            let primary = baseFactory(label)\n            let fileHandler = FileLogHandler(label: label)\n            var handler = CompositeLogHandler(primary: primary, secondary: fileHandler)\n            handler.logLevel = .trace\n            return handler\n        }\n\n        self.isBootstrapped = true\n    }\n\n    public static func logger(_ category: String) -> CodexBarLogger {\n        let logger = Logger(label: \"com.steipete.codexbar.\\(category)\")\n        return CodexBarLogger { level, message, metadata in\n            guard self.shouldLog(level) else { return }\n            let swiftLogLevel = level.asSwiftLogLevel\n            let safeMessage = LogRedactor.redact(message)\n            let meta = metadata?.reduce(into: Logger.Metadata()) { partial, entry in\n                partial[entry.key] = .string(LogRedactor.redact(entry.value))\n            }\n            logger.log(level: swiftLogLevel, \"\\(safeMessage)\", metadata: meta)\n        }\n    }\n\n    public static func parseLevel(_ raw: String?) -> Level? {\n        guard let raw, !raw.isEmpty else { return nil }\n        return Level(rawValue: raw.trimmingCharacters(in: .whitespacesAndNewlines).lowercased())\n    }\n\n    public static func setLogLevel(_ level: Level) {\n        self.levelLock.lock()\n        self.currentLevel = level\n        self.levelLock.unlock()\n        let logger = self.logger(LogCategories.logging)\n        logger.info(\"Log level set to \\(level.rawValue)\")\n    }\n\n    public static func currentLogLevel() -> Level {\n        self.levelLock.lock()\n        defer { self.levelLock.unlock() }\n        return self.currentLevel\n    }\n\n    private static func shouldLog(_ level: Level) -> Bool {\n        level.rank >= self.currentLogLevel().rank\n    }\n\n    public static var fileLogURL: URL {\n        FileLogSink.defaultURL\n    }\n\n    public static func setFileLoggingEnabled(_ enabled: Bool) {\n        FileLogSink.shared.setEnabled(enabled, fileURL: self.fileLogURL)\n        let state = enabled ? \"enabled\" : \"disabled\"\n        let logger = self.logger(LogCategories.logging)\n        logger.info(\"File logging \\(state)\", metadata: [\"path\": self.fileLogURL.path])\n    }\n}\n\npublic struct CodexBarLogger: Sendable {\n    private let logFn: @Sendable (CodexBarLog.Level, String, [String: String]?) -> Void\n\n    fileprivate init(_ logFn: @escaping @Sendable (CodexBarLog.Level, String, [String: String]?) -> Void) {\n        self.logFn = logFn\n    }\n\n    public func trace(_ message: @autoclosure () -> String, metadata: [String: String]? = nil) {\n        self.logFn(.trace, message(), metadata)\n    }\n\n    public func verbose(_ message: @autoclosure () -> String, metadata: [String: String]? = nil) {\n        self.logFn(.verbose, message(), metadata)\n    }\n\n    public func debug(_ message: @autoclosure () -> String, metadata: [String: String]? = nil) {\n        self.logFn(.debug, message(), metadata)\n    }\n\n    public func info(_ message: @autoclosure () -> String, metadata: [String: String]? = nil) {\n        self.logFn(.info, message(), metadata)\n    }\n\n    public func warning(_ message: @autoclosure () -> String, metadata: [String: String]? = nil) {\n        self.logFn(.warning, message(), metadata)\n    }\n\n    public func error(_ message: @autoclosure () -> String, metadata: [String: String]? = nil) {\n        self.logFn(.error, message(), metadata)\n    }\n\n    public func critical(_ message: @autoclosure () -> String, metadata: [String: String]? = nil) {\n        self.logFn(.critical, message(), metadata)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Logging/CompositeLogHandler.swift",
    "content": "import Logging\n\nstruct CompositeLogHandler: LogHandler {\n    private var primary: any LogHandler\n    private var secondary: any LogHandler\n\n    init(primary: any LogHandler, secondary: any LogHandler) {\n        self.primary = primary\n        self.secondary = secondary\n    }\n\n    var metadata: Logger.Metadata {\n        get { self.primary.metadata }\n        set {\n            self.primary.metadata = newValue\n            self.secondary.metadata = newValue\n        }\n    }\n\n    var logLevel: Logger.Level {\n        get { self.primary.logLevel }\n        set {\n            self.primary.logLevel = newValue\n            self.secondary.logLevel = newValue\n        }\n    }\n\n    subscript(metadataKey metadataKey: String) -> Logger.Metadata.Value? {\n        get { self.primary[metadataKey: metadataKey] }\n        set {\n            self.primary[metadataKey: metadataKey] = newValue\n            self.secondary[metadataKey: metadataKey] = newValue\n        }\n    }\n\n    // swiftlint:disable:next function_parameter_count\n    func log(\n        level: Logger.Level,\n        message: Logger.Message,\n        metadata: Logger.Metadata?,\n        source: String,\n        file: String,\n        function: String,\n        line: UInt)\n    {\n        self.primary.log(\n            level: level,\n            message: message,\n            metadata: metadata,\n            source: source,\n            file: file,\n            function: function,\n            line: line)\n        self.secondary.log(\n            level: level,\n            message: message,\n            metadata: metadata,\n            source: source,\n            file: file,\n            function: function,\n            line: line)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Logging/FileLogHandler.swift",
    "content": "import Foundation\nimport Logging\n\nfinal class FileLogSink: @unchecked Sendable {\n    static let shared = FileLogSink()\n    static let defaultURL: URL = {\n        let base = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first\n            ?? FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent(\"Library\", isDirectory: true)\n        return base\n            .appendingPathComponent(\"Logs\", isDirectory: true)\n            .appendingPathComponent(\"CodexBar\", isDirectory: true)\n            .appendingPathComponent(\"CodexBar.log\")\n    }()\n\n    private let queue = DispatchQueue(label: \"com.steipete.codexbar.filelog\", qos: .utility)\n    private let fileManager: FileManager\n    private var isEnabled = false\n    private var fileHandle: FileHandle?\n    private var fileURL: URL = FileLogSink.defaultURL\n    private let maxBytes: Int64 = 10 * 1024 * 1024\n\n    init(fileManager: FileManager = .default) {\n        self.fileManager = fileManager\n    }\n\n    func setEnabled(_ enabled: Bool, fileURL: URL = FileLogSink.defaultURL) {\n        self.queue.async {\n            self.isEnabled = enabled\n            self.fileURL = fileURL\n            if !enabled {\n                self.closeHandle()\n                return\n            }\n            _ = self.openHandleIfNeeded()\n        }\n    }\n\n    func currentURL() -> URL {\n        self.queue.sync { self.fileURL }\n    }\n\n    func write(_ text: String) {\n        self.queue.async {\n            guard self.isEnabled else { return }\n            guard let data = text.data(using: .utf8) else { return }\n            guard let handle = self.openHandleIfNeeded() else { return }\n            handle.write(data)\n        }\n    }\n\n    private func openHandleIfNeeded() -> FileHandle? {\n        if let handle = self.fileHandle { return handle }\n        do {\n            try self.prepareFile(at: self.fileURL)\n            let handle = try FileHandle(forWritingTo: self.fileURL)\n            handle.seekToEndOfFile()\n            self.fileHandle = handle\n            return handle\n        } catch {\n            return nil\n        }\n    }\n\n    private func prepareFile(at url: URL) throws {\n        let directory = url.deletingLastPathComponent()\n        if !self.fileManager.fileExists(atPath: directory.path) {\n            try self.fileManager.createDirectory(at: directory, withIntermediateDirectories: true)\n        }\n        if self.fileManager.fileExists(atPath: url.path) {\n            let attributes = try self.fileManager.attributesOfItem(atPath: url.path)\n            let size = (attributes[.size] as? NSNumber)?.int64Value ?? 0\n            if size > self.maxBytes {\n                try self.fileManager.removeItem(at: url)\n            }\n        }\n        if !self.fileManager.fileExists(atPath: url.path) {\n            _ = self.fileManager.createFile(atPath: url.path, contents: nil)\n        }\n    }\n\n    private func closeHandle() {\n        if let handle = self.fileHandle {\n            try? handle.close()\n        }\n        self.fileHandle = nil\n    }\n}\n\nstruct FileLogHandler: LogHandler {\n    var metadata: Logger.Metadata = [:]\n    var logLevel: Logger.Level = .info\n\n    private let label: String\n    private let sink: FileLogSink\n\n    init(label: String, sink: FileLogSink = .shared) {\n        self.label = label\n        self.sink = sink\n    }\n\n    subscript(metadataKey metadataKey: String) -> Logger.Metadata.Value? {\n        get { self.metadata[metadataKey] }\n        set { self.metadata[metadataKey] = newValue }\n    }\n\n    // swiftlint:disable:next function_parameter_count\n    func log(\n        level: Logger.Level,\n        message: Logger.Message,\n        metadata: Logger.Metadata?,\n        source: String,\n        file: String,\n        function: String,\n        line: UInt)\n    {\n        let ts = Self.timestamp()\n        var combined = self.metadata\n        if let metadata { combined.merge(metadata, uniquingKeysWith: { _, new in new }) }\n        var metaText = \"\"\n        if !combined.isEmpty {\n            let pairs = combined\n                .sorted(by: { $0.key < $1.key })\n                .map { key, value in\n                    let rendered = Self.renderMetadataValue(value)\n                    let safeValue = LogRedactor.redact(rendered)\n                    return \"\\(key)=\\(safeValue)\"\n                }\n                .joined(separator: \" \")\n            metaText = \" \\(pairs)\"\n        }\n        let safeMessage = LogRedactor.redact(\"\\(message)\")\n        let lineText = \"[\\(ts)] [\\(level.rawValue.uppercased())] \\(self.label): \\(safeMessage)\\(metaText)\\n\"\n        _ = source\n        _ = file\n        _ = function\n        _ = line\n        self.sink.write(lineText)\n    }\n\n    private static func timestamp() -> String {\n        let formatter = ISO8601DateFormatter()\n        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]\n        return formatter.string(from: Date())\n    }\n\n    private static func renderMetadataValue(_ value: Logger.Metadata.Value) -> String {\n        switch value {\n        case let .string(text):\n            text\n        default:\n            String(describing: value)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Logging/JSONStderrLogHandler.swift",
    "content": "import Foundation\nimport Logging\n#if canImport(Darwin)\nimport Darwin\n#else\nimport Glibc\n#endif\n\nstruct JSONStderrLogHandler: LogHandler {\n    var metadata: Logger.Metadata = [:]\n    var logLevel: Logger.Level = .info\n\n    private let label: String\n    private let encoder: JSONEncoder\n\n    init(label: String) {\n        self.label = label\n        self.encoder = JSONEncoder()\n        self.encoder.dateEncodingStrategy = .iso8601\n        self.encoder.outputFormatting = [.sortedKeys]\n    }\n\n    subscript(metadataKey metadataKey: String) -> Logger.Metadata.Value? {\n        get { self.metadata[metadataKey] }\n        set { self.metadata[metadataKey] = newValue }\n    }\n\n    // swiftlint:disable:next function_parameter_count\n    func log(\n        level: Logger.Level,\n        message: Logger.Message,\n        metadata: Logger.Metadata?,\n        source: String,\n        file: String,\n        function: String,\n        line: UInt)\n    {\n        let ts = Date()\n        var combined = self.metadata\n        if let metadata { combined.merge(metadata, uniquingKeysWith: { _, new in new }) }\n\n        let payload = JSONLogLine(\n            timestamp: ts,\n            level: level.rawValue,\n            label: self.label,\n            message: message.description,\n            source: source,\n            file: file,\n            function: function,\n            line: line,\n            metadata: combined.isEmpty ? nil : combined.mapValues(\\.description))\n\n        guard let data = try? self.encoder.encode(payload),\n              let text = String(data: data, encoding: .utf8) else { return }\n        Self.writeStderr(text + \"\\n\")\n    }\n}\n\nprivate struct JSONLogLine: Encodable {\n    let timestamp: Date\n    let level: String\n    let label: String\n    let message: String\n    let source: String\n    let file: String\n    let function: String\n    let line: UInt\n    let metadata: [String: String]?\n}\n\nextension JSONStderrLogHandler {\n    private static func writeStderr(_ text: String) {\n        let bytes = Array(text.utf8)\n        bytes.withUnsafeBytes { buffer in\n            guard let baseAddress = buffer.baseAddress else { return }\n            _ = write(STDERR_FILENO, baseAddress, buffer.count)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Logging/LogCategories.swift",
    "content": "public enum LogCategories {\n    public static let amp = \"amp\"\n    public static let antigravity = \"antigravity\"\n    public static let app = \"app\"\n    public static let auggieCLI = \"auggie-cli\"\n    public static let augment = \"augment\"\n    public static let augmentKeepalive = \"augment-keepalive\"\n    public static let browserCookieGate = \"browser-cookie-gate\"\n    public static let claudeCLI = \"claude-cli\"\n    public static let claudeProbe = \"claude-probe\"\n    public static let claudeUsage = \"claude-usage\"\n    public static let codexRPC = \"codex-rpc\"\n    public static let configMigration = \"config-migration\"\n    public static let configStore = \"config-store\"\n    public static let cookieCache = \"cookie-cache\"\n    public static let cookieHeaderStore = \"cookie-header-store\"\n    public static let copilotTokenStore = \"copilot-token-store\"\n    public static let creditsPurchase = \"creditsPurchase\"\n    public static let cursorLogin = \"cursor-login\"\n    public static let geminiProbe = \"gemini-probe\"\n    public static let keychainCache = \"keychain-cache\"\n    public static let keychainMigration = \"keychain-migration\"\n    public static let keychainPreflight = \"keychain-preflight\"\n    public static let keychainPrompt = \"keychain-prompt\"\n    public static let kimiAPI = \"kimi-api\"\n    public static let kimiCookie = \"kimi-cookie\"\n    public static let kimiK2TokenStore = \"kimi-k2-token-store\"\n    public static let kimiK2Usage = \"kimi-k2-usage\"\n    public static let kimiTokenStore = \"kimi-token-store\"\n    public static let kimiWeb = \"kimi-web\"\n    public static let kiro = \"kiro\"\n    public static let launchAtLogin = \"launch-at-login\"\n    public static let login = \"login\"\n    public static let logging = \"logging\"\n    public static let minimaxAPITokenStore = \"minimax-api-token-store\"\n    public static let minimaxCookie = \"minimax-cookie\"\n    public static let minimaxCookieStore = \"minimax-cookie-store\"\n    public static let minimaxUsage = \"minimax-usage\"\n    public static let minimaxWeb = \"minimax-web\"\n    public static let notifications = \"notifications\"\n    public static let openAIWeb = \"openai-web\"\n    public static let openAIWebview = \"openai-webview\"\n    public static let ollama = \"ollama\"\n    public static let opencodeUsage = \"opencode-usage\"\n    public static let openRouterUsage = \"openrouter-usage\"\n    public static let providerDetection = \"provider-detection\"\n    public static let providers = \"providers\"\n    public static let sessionQuota = \"sessionQuota\"\n    public static let sessionQuotaNotifications = \"sessionQuotaNotifications\"\n    public static let settings = \"settings\"\n    public static let subprocess = \"subprocess\"\n    public static let syntheticTokenStore = \"synthetic-token-store\"\n    public static let syntheticUsage = \"synthetic-usage\"\n    public static let terminal = \"terminal\"\n    public static let tokenAccounts = \"token-accounts\"\n    public static let tokenCost = \"token-cost\"\n    public static let ttyRunner = \"tty-runner\"\n    public static let vertexAIFetcher = \"vertexai-fetcher\"\n    public static let warpUsage = \"warp-usage\"\n    public static let webkitTeardown = \"webkit-teardown\"\n    public static let zaiSettings = \"zai-settings\"\n    public static let zaiTokenStore = \"zai-token-store\"\n    public static let zaiUsage = \"zai-usage\"\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Logging/LogMetadata.swift",
    "content": "import Foundation\n\npublic enum LogMetadata {\n    public static func secretSummary(_ value: String?) -> [String: String] {\n        let trimmed = value?.trimmingCharacters(in: .whitespacesAndNewlines) ?? \"\"\n        let state = trimmed.isEmpty ? \"cleared\" : \"set\"\n        let length = trimmed.count\n        return [\"state\": state, \"length\": \"\\(length)\"]\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Logging/LogRedactor.swift",
    "content": "import Foundation\n\npublic enum LogRedactor {\n    private static let fallbackRegex: NSRegularExpression = {\n        do {\n            return try NSRegularExpression(pattern: \"$^\", options: [])\n        } catch {\n            fatalError(\"Failed to build fallback regex: \\(error)\")\n        }\n    }()\n\n    private static let emailRegex = Self.makeRegex(\n        pattern: #\"[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}\"#,\n        options: [.caseInsensitive])\n    private static let cookieHeaderRegex = Self.makeRegex(\n        pattern: #\"(?i)(cookie\\s*:\\s*)([^\\r\\n]+)\"#)\n    private static let authorizationRegex = Self.makeRegex(\n        pattern: #\"(?i)(authorization\\s*:\\s*)([^\\r\\n]+)\"#)\n    private static let bearerRegex = Self.makeRegex(\n        pattern: #\"(?i)\\bbearer\\s+[a-z0-9._\\-]+=*\\b\"#)\n\n    public static func redact(_ text: String) -> String {\n        var output = text\n        output = self.replace(self.emailRegex, in: output, with: \"<redacted-email>\")\n        output = self.replace(self.cookieHeaderRegex, in: output, with: \"$1<redacted>\")\n        output = self.replace(self.authorizationRegex, in: output, with: \"$1<redacted>\")\n        output = self.replace(self.bearerRegex, in: output, with: \"Bearer <redacted>\")\n        return output\n    }\n\n    private static func makeRegex(pattern: String, options: NSRegularExpression.Options = []) -> NSRegularExpression {\n        (try? NSRegularExpression(pattern: pattern, options: options)) ?? self.fallbackRegex\n    }\n\n    private static func replace(_ regex: NSRegularExpression, in text: String, with template: String) -> String {\n        let range = NSRange(text.startIndex..<text.endIndex, in: text)\n        return regex.stringByReplacingMatches(in: text, options: [], range: range, withTemplate: template)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Logging/OSLogLogHandler.swift",
    "content": "#if canImport(os)\nimport Foundation\nimport Logging\nimport os\n\nstruct OSLogLogHandler: LogHandler {\n    var metadata: Logging.Logger.Metadata = [:]\n    var logLevel: Logging.Logger.Level = .info\n\n    private let label: String\n    private let subsystem: String\n    private let logger: os.Logger\n\n    init(label: String, subsystem: String) {\n        self.label = label\n        self.subsystem = subsystem\n        let category = Self.category(from: label, subsystem: subsystem)\n        self.logger = os.Logger(subsystem: subsystem, category: category)\n    }\n\n    subscript(metadataKey metadataKey: String) -> Logging.Logger.Metadata.Value? {\n        get { self.metadata[metadataKey] }\n        set { self.metadata[metadataKey] = newValue }\n    }\n\n    // swiftlint:disable:next function_parameter_count\n    func log(\n        level: Logging.Logger.Level,\n        message: Logging.Logger.Message,\n        metadata: Logging.Logger.Metadata?,\n        source: String,\n        file: String,\n        function: String,\n        line: UInt)\n    {\n        let msg = Self.decorate(\n            message: message.description,\n            label: self.label,\n            subsystem: self.subsystem,\n            metadata: self.metadata,\n            extraMetadata: metadata)\n\n        switch level {\n        case .trace:\n            self.logger.debug(\"\\(msg, privacy: .public)\")\n        case .debug:\n            self.logger.debug(\"\\(msg, privacy: .public)\")\n        case .info, .notice:\n            self.logger.info(\"\\(msg, privacy: .public)\")\n        case .warning:\n            self.logger.warning(\"\\(msg, privacy: .public)\")\n        case .error:\n            self.logger.error(\"\\(msg, privacy: .public)\")\n        case .critical:\n            self.logger.fault(\"\\(msg, privacy: .public)\")\n        }\n    }\n\n    private static func category(from label: String, subsystem: String) -> String {\n        let prefix = subsystem + \".\"\n        guard label.hasPrefix(prefix) else { return label }\n        return String(label.dropFirst(prefix.count))\n    }\n\n    private static func decorate(\n        message: String,\n        label: String,\n        subsystem: String,\n        metadata: Logging.Logger.Metadata,\n        extraMetadata: Logging.Logger.Metadata?)\n        -> String\n    {\n        var merged = metadata\n        if let extraMetadata { merged.merge(extraMetadata, uniquingKeysWith: { _, new in new }) }\n        guard !merged.isEmpty else { return message }\n\n        let suffix = merged\n            .sorted(by: { $0.key < $1.key })\n            .map { \"\\($0.key)=\\($0.value)\" }\n            .joined(separator: \" \")\n        _ = label\n        _ = subsystem\n        return \"\\(message) (\\(suffix))\"\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/CodexBarCore/Logging/ProviderLogging.swift",
    "content": "public enum ProviderLogging {\n    public static func logStartupState(\n        logger: CodexBarLogger,\n        providers: [UsageProvider],\n        isEnabled: (UsageProvider) -> Bool,\n        modeSnapshot: [String: String])\n    {\n        let ordered = providers.sorted { $0.rawValue < $1.rawValue }\n        let states = ordered\n            .map { provider -> String in\n                let enabled = isEnabled(provider)\n                return \"\\(provider.rawValue)=\\(enabled ? \"1\" : \"0\")\"\n            }\n            .joined(separator: \",\")\n        let enabledProviders = ordered\n            .filter { isEnabled($0) }\n            .map(\\.rawValue)\n            .joined(separator: \",\")\n        logger.info(\n            \"Provider enablement at startup\",\n            metadata: [\n                \"states\": states,\n                \"enabled\": enabledProviders.isEmpty ? \"none\" : enabledProviders,\n            ])\n        logger.info(\"Provider mode snapshot\", metadata: modeSnapshot)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/OpenAIDashboardModels.swift",
    "content": "import Foundation\n\npublic struct OpenAIDashboardSnapshot: Codable, Equatable, Sendable {\n    public let signedInEmail: String?\n    public let codeReviewRemainingPercent: Double?\n    public let creditEvents: [CreditEvent]\n    public let dailyBreakdown: [OpenAIDashboardDailyBreakdown]\n    /// Usage breakdown time series from the Codex dashboard chart (\"Usage breakdown\", 30 days).\n    ///\n    /// This is distinct from `dailyBreakdown`, which is derived from `creditEvents` (credits usage history table).\n    public let usageBreakdown: [OpenAIDashboardDailyBreakdown]\n    public let creditsPurchaseURL: String?\n    public let primaryLimit: RateWindow?\n    public let secondaryLimit: RateWindow?\n    public let creditsRemaining: Double?\n    public let accountPlan: String?\n    public let updatedAt: Date\n\n    public init(\n        signedInEmail: String?,\n        codeReviewRemainingPercent: Double?,\n        creditEvents: [CreditEvent],\n        dailyBreakdown: [OpenAIDashboardDailyBreakdown],\n        usageBreakdown: [OpenAIDashboardDailyBreakdown],\n        creditsPurchaseURL: String?,\n        primaryLimit: RateWindow? = nil,\n        secondaryLimit: RateWindow? = nil,\n        creditsRemaining: Double? = nil,\n        accountPlan: String? = nil,\n        updatedAt: Date)\n    {\n        self.signedInEmail = signedInEmail\n        self.codeReviewRemainingPercent = codeReviewRemainingPercent\n        self.creditEvents = creditEvents\n        self.dailyBreakdown = dailyBreakdown\n        self.usageBreakdown = usageBreakdown\n        self.creditsPurchaseURL = creditsPurchaseURL\n        self.primaryLimit = primaryLimit\n        self.secondaryLimit = secondaryLimit\n        self.creditsRemaining = creditsRemaining\n        self.accountPlan = accountPlan\n        self.updatedAt = updatedAt\n    }\n\n    private enum CodingKeys: String, CodingKey {\n        case signedInEmail\n        case codeReviewRemainingPercent\n        case creditEvents\n        case dailyBreakdown\n        case usageBreakdown\n        case creditsPurchaseURL\n        case primaryLimit\n        case secondaryLimit\n        case creditsRemaining\n        case accountPlan\n        case updatedAt\n    }\n\n    public init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        self.signedInEmail = try container.decodeIfPresent(String.self, forKey: .signedInEmail)\n        self.codeReviewRemainingPercent = try container.decodeIfPresent(\n            Double.self,\n            forKey: .codeReviewRemainingPercent)\n        self.creditEvents = try container.decodeIfPresent([CreditEvent].self, forKey: .creditEvents) ?? []\n        self.dailyBreakdown = try container.decodeIfPresent(\n            [OpenAIDashboardDailyBreakdown].self,\n            forKey: .dailyBreakdown)\n            ?? Self.makeDailyBreakdown(from: self.creditEvents, maxDays: 30)\n        self.usageBreakdown = try container.decodeIfPresent(\n            [OpenAIDashboardDailyBreakdown].self,\n            forKey: .usageBreakdown) ?? []\n        self.creditsPurchaseURL = try container.decodeIfPresent(String.self, forKey: .creditsPurchaseURL)\n        self.primaryLimit = try container.decodeIfPresent(RateWindow.self, forKey: .primaryLimit)\n        self.secondaryLimit = try container.decodeIfPresent(RateWindow.self, forKey: .secondaryLimit)\n        self.creditsRemaining = try container.decodeIfPresent(Double.self, forKey: .creditsRemaining)\n        self.accountPlan = try container.decodeIfPresent(String.self, forKey: .accountPlan)\n        self.updatedAt = try container.decode(Date.self, forKey: .updatedAt)\n    }\n\n    public static func makeDailyBreakdown(from events: [CreditEvent], maxDays: Int) -> [OpenAIDashboardDailyBreakdown] {\n        guard !events.isEmpty else { return [] }\n\n        let calendar = Calendar(identifier: .gregorian)\n        let formatter = DateFormatter()\n        formatter.locale = Locale(identifier: \"en_US_POSIX\")\n        formatter.calendar = calendar\n        formatter.timeZone = TimeZone.current\n        formatter.dateFormat = \"yyyy-MM-dd\"\n\n        var totals: [String: [String: Double]] = [:] // day -> service -> credits\n        for event in events {\n            let day = formatter.string(from: event.date)\n            totals[day, default: [:]][event.service, default: 0] += event.creditsUsed\n        }\n\n        let sortedDays = totals.keys.sorted(by: >).prefix(maxDays)\n        return sortedDays.map { day in\n            let serviceTotals = totals[day] ?? [:]\n            let services = serviceTotals\n                .map { OpenAIDashboardServiceUsage(service: $0.key, creditsUsed: $0.value) }\n                .sorted { lhs, rhs in\n                    if lhs.creditsUsed == rhs.creditsUsed { return lhs.service < rhs.service }\n                    return lhs.creditsUsed > rhs.creditsUsed\n                }\n            let total = services.reduce(0) { $0 + $1.creditsUsed }\n            return OpenAIDashboardDailyBreakdown(day: day, services: services, totalCreditsUsed: total)\n        }\n    }\n}\n\nextension OpenAIDashboardSnapshot {\n    public func toUsageSnapshot(\n        provider: UsageProvider = .codex,\n        accountEmail: String? = nil,\n        accountPlan: String? = nil) -> UsageSnapshot?\n    {\n        guard let primaryLimit else { return nil }\n        let resolvedEmail = accountEmail ?? self.signedInEmail\n        let resolvedPlan = accountPlan ?? self.accountPlan\n        let identity = ProviderIdentitySnapshot(\n            providerID: provider,\n            accountEmail: resolvedEmail,\n            accountOrganization: nil,\n            loginMethod: resolvedPlan)\n        return UsageSnapshot(\n            primary: primaryLimit,\n            secondary: self.secondaryLimit,\n            tertiary: nil,\n            providerCost: nil,\n            updatedAt: self.updatedAt,\n            identity: identity)\n    }\n\n    public func toCreditsSnapshot() -> CreditsSnapshot? {\n        guard let creditsRemaining else { return nil }\n        return CreditsSnapshot(remaining: creditsRemaining, events: self.creditEvents, updatedAt: self.updatedAt)\n    }\n}\n\npublic struct OpenAIDashboardDailyBreakdown: Codable, Equatable, Sendable {\n    /// Day key in `yyyy-MM-dd` (local time).\n    public let day: String\n    public let services: [OpenAIDashboardServiceUsage]\n    public let totalCreditsUsed: Double\n\n    public init(day: String, services: [OpenAIDashboardServiceUsage], totalCreditsUsed: Double) {\n        self.day = day\n        self.services = services\n        self.totalCreditsUsed = totalCreditsUsed\n    }\n}\n\npublic struct OpenAIDashboardServiceUsage: Codable, Equatable, Sendable {\n    public let service: String\n    public let creditsUsed: Double\n\n    public init(service: String, creditsUsed: Double) {\n        self.service = service\n        self.creditsUsed = creditsUsed\n    }\n}\n\npublic struct OpenAIDashboardCache: Codable, Equatable, Sendable {\n    public let accountEmail: String\n    public let snapshot: OpenAIDashboardSnapshot\n\n    public init(accountEmail: String, snapshot: OpenAIDashboardSnapshot) {\n        self.accountEmail = accountEmail\n        self.snapshot = snapshot\n    }\n}\n\npublic enum OpenAIDashboardCacheStore {\n    public static func load() -> OpenAIDashboardCache? {\n        guard let url = self.cacheURL else { return nil }\n        guard let data = try? Data(contentsOf: url) else { return nil }\n        let decoder = JSONDecoder()\n        decoder.dateDecodingStrategy = .iso8601\n        return try? decoder.decode(OpenAIDashboardCache.self, from: data)\n    }\n\n    public static func save(_ cache: OpenAIDashboardCache) {\n        guard let url = self.cacheURL else { return }\n        do {\n            try FileManager.default.createDirectory(\n                at: url.deletingLastPathComponent(),\n                withIntermediateDirectories: true)\n            let encoder = JSONEncoder()\n            encoder.dateEncodingStrategy = .iso8601\n            let data = try encoder.encode(cache)\n            try data.write(to: url, options: [.atomic])\n        } catch {\n            // Best-effort cache only; ignore errors.\n        }\n    }\n\n    public static func clear() {\n        guard let url = self.cacheURL else { return }\n        try? FileManager.default.removeItem(at: url)\n    }\n\n    private static var cacheURL: URL? {\n        guard let root = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first else {\n            return nil\n        }\n        let dir = root.appendingPathComponent(\"com.steipete.codexbar\", isDirectory: true)\n        return dir.appendingPathComponent(\"openai-dashboard.json\")\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/OpenAIWeb/OpenAIDashboardBrowserCookieImporter.swift",
    "content": "#if os(macOS)\nimport Foundation\nimport SweetCookieKit\nimport WebKit\n\n@MainActor\npublic struct OpenAIDashboardBrowserCookieImporter {\n    public struct FoundAccount: Sendable, Hashable {\n        public let sourceLabel: String\n        public let email: String\n\n        public init(sourceLabel: String, email: String) {\n            self.sourceLabel = sourceLabel\n            self.email = email\n        }\n    }\n\n    public enum ImportError: LocalizedError {\n        case noCookiesFound\n        case browserAccessDenied(details: String)\n        case dashboardStillRequiresLogin\n        case noMatchingAccount(found: [FoundAccount])\n        case manualCookieHeaderInvalid\n\n        public var errorDescription: String? {\n            switch self {\n            case .noCookiesFound:\n                return \"No browser cookies found.\"\n            case let .browserAccessDenied(details):\n                return \"Browser cookie access denied. \\(details)\"\n            case .dashboardStillRequiresLogin:\n                return \"Browser cookies imported, but dashboard still requires login.\"\n            case let .noMatchingAccount(found):\n                if found.isEmpty { return \"No matching OpenAI web session found in browsers.\" }\n                let display = found\n                    .sorted { lhs, rhs in\n                        if lhs.sourceLabel == rhs.sourceLabel { return lhs.email < rhs.email }\n                        return lhs.sourceLabel < rhs.sourceLabel\n                    }\n                    .map { \"\\($0.sourceLabel)=\\($0.email)\" }\n                    .joined(separator: \", \")\n                return \"OpenAI web session does not match Codex account. Found: \\(display).\"\n            case .manualCookieHeaderInvalid:\n                return \"Manual cookie header is missing a valid OpenAI session cookie.\"\n            }\n        }\n    }\n\n    public struct ImportResult: Sendable {\n        public let sourceLabel: String\n        public let cookieCount: Int\n        public let signedInEmail: String?\n        public let matchesCodexEmail: Bool\n\n        public init(\n            sourceLabel: String,\n            cookieCount: Int,\n            signedInEmail: String?,\n            matchesCodexEmail: Bool)\n        {\n            self.sourceLabel = sourceLabel\n            self.cookieCount = cookieCount\n            self.signedInEmail = signedInEmail\n            self.matchesCodexEmail = matchesCodexEmail\n        }\n    }\n\n    public init(browserDetection: BrowserDetection) {\n        self.browserDetection = browserDetection\n    }\n\n    private let browserDetection: BrowserDetection\n\n    private struct ImportDiagnostics {\n        var mismatches: [FoundAccount] = []\n        var foundAnyCookies: Bool = false\n        var foundUnknownEmail: Bool = false\n        var accessDeniedHints: [String] = []\n    }\n\n    private static let cookieDomains = [\"chatgpt.com\", \"openai.com\"]\n    private static let cookieClient = BrowserCookieClient()\n    private static let cookieImportOrder: BrowserCookieImportOrder =\n        ProviderDefaults.metadata[.codex]?.browserCookieOrder ?? Browser.defaultImportOrder\n\n    private enum CandidateEvaluation {\n        case match(candidate: Candidate, signedInEmail: String)\n        case mismatch(candidate: Candidate, signedInEmail: String)\n        case loggedIn(candidate: Candidate, signedInEmail: String)\n        case unknown(candidate: Candidate)\n        case loginRequired(candidate: Candidate)\n    }\n\n    public func importBestCookies(\n        intoAccountEmail targetEmail: String?,\n        allowAnyAccount: Bool = false,\n        logger: ((String) -> Void)? = nil) async throws -> ImportResult\n    {\n        let log: (String) -> Void = { message in\n            logger?(\"[web] \\(message)\")\n        }\n\n        let targetEmail = targetEmail?.trimmingCharacters(in: .whitespacesAndNewlines)\n        let normalizedTarget = targetEmail?.isEmpty == false ? targetEmail : nil\n\n        if normalizedTarget != nil {\n            log(\"Codex email known; matching required.\")\n        } else {\n            guard allowAnyAccount else {\n                throw ImportError.noCookiesFound\n            }\n            log(\"Codex email unknown; importing any signed-in session.\")\n        }\n\n        var diagnostics = ImportDiagnostics()\n\n        if let cached = CookieHeaderCache.load(provider: .codex),\n           !cached.cookieHeader.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty\n        {\n            log(\"Using cached cookie header from \\(cached.sourceLabel)\")\n            do {\n                return try await self.importManualCookies(\n                    cookieHeader: cached.cookieHeader,\n                    intoAccountEmail: normalizedTarget,\n                    allowAnyAccount: allowAnyAccount,\n                    logger: log)\n            } catch let error as ImportError {\n                switch error {\n                case .manualCookieHeaderInvalid, .noMatchingAccount, .dashboardStillRequiresLogin:\n                    CookieHeaderCache.clear(provider: .codex)\n                default:\n                    throw error\n                }\n            } catch {\n                throw error\n            }\n        }\n\n        // Filter to cookie-eligible browsers to avoid unnecessary keychain prompts\n        let installedBrowsers = Self.cookieImportOrder.cookieImportCandidates(using: self.browserDetection)\n        for browserSource in installedBrowsers {\n            if let match = await self.trySource(\n                browserSource,\n                targetEmail: normalizedTarget,\n                allowAnyAccount: allowAnyAccount,\n                log: log,\n                diagnostics: &diagnostics)\n            {\n                return match\n            }\n        }\n\n        if !diagnostics.mismatches.isEmpty {\n            let found = Array(Set(diagnostics.mismatches)).sorted { lhs, rhs in\n                if lhs.sourceLabel == rhs.sourceLabel { return lhs.email < rhs.email }\n                return lhs.sourceLabel < rhs.sourceLabel\n            }\n            log(\"No matching browser session found. Candidate count: \\(found.count)\")\n            throw ImportError.noMatchingAccount(found: found)\n        }\n\n        if diagnostics.foundUnknownEmail || diagnostics.foundAnyCookies {\n            log(\"No matching browser session found (email unknown).\")\n            throw ImportError.noMatchingAccount(found: [])\n        }\n\n        if !diagnostics.accessDeniedHints.isEmpty {\n            let details = diagnostics.accessDeniedHints.joined(separator: \" \")\n            log(\"Cookie access denied: \\(details)\")\n            throw ImportError.browserAccessDenied(details: details)\n        }\n\n        throw ImportError.noCookiesFound\n    }\n\n    public func importManualCookies(\n        cookieHeader: String,\n        intoAccountEmail targetEmail: String?,\n        allowAnyAccount: Bool = false,\n        logger: ((String) -> Void)? = nil) async throws -> ImportResult\n    {\n        let log: (String) -> Void = { message in\n            logger?(\"[web] \\(message)\")\n        }\n        let normalizedTarget = targetEmail?.trimmingCharacters(in: .whitespacesAndNewlines)\n        let allowAnyAccount = allowAnyAccount || normalizedTarget == nil || normalizedTarget?.isEmpty == true\n\n        guard let normalized = CookieHeaderNormalizer.normalize(cookieHeader) else {\n            throw ImportError.manualCookieHeaderInvalid\n        }\n        let pairs = CookieHeaderNormalizer.pairs(from: normalized)\n        guard !pairs.isEmpty else { throw ImportError.manualCookieHeaderInvalid }\n        let cookies = self.cookies(from: pairs)\n        guard !cookies.isEmpty else { throw ImportError.manualCookieHeaderInvalid }\n\n        let candidate = Candidate(label: \"Manual\", cookies: cookies)\n        switch await self.evaluateCandidate(\n            candidate,\n            targetEmail: normalizedTarget,\n            allowAnyAccount: allowAnyAccount,\n            log: log)\n        {\n        case let .match(_, signedInEmail):\n            return try await self.persist(candidate: candidate, targetEmail: signedInEmail, logger: log)\n        case let .loggedIn(_, signedInEmail):\n            return try await self.persist(candidate: candidate, targetEmail: signedInEmail, logger: log)\n        case let .mismatch(_, signedInEmail):\n            throw ImportError.noMatchingAccount(found: [FoundAccount(sourceLabel: \"Manual\", email: signedInEmail)])\n        case .unknown:\n            if allowAnyAccount {\n                return try await self.persistToDefaultStore(candidate: candidate, logger: log)\n            }\n            throw ImportError.noMatchingAccount(found: [])\n        case .loginRequired:\n            throw ImportError.manualCookieHeaderInvalid\n        }\n    }\n\n    private func trySafari(\n        targetEmail: String?,\n        allowAnyAccount: Bool,\n        log: @escaping (String) -> Void,\n        diagnostics: inout ImportDiagnostics) async -> ImportResult?\n    {\n        // Safari first: avoids touching Keychain (\"Chrome Safe Storage\") when Safari already matches.\n        do {\n            let query = BrowserCookieQuery(domains: Self.cookieDomains)\n            let sources = try Self.cookieClient.records(\n                matching: query,\n                in: .safari,\n                logger: log)\n            guard !sources.isEmpty else {\n                log(\"Safari contained 0 matching records.\")\n                return nil\n            }\n            for source in sources {\n                let cookies = BrowserCookieClient.makeHTTPCookies(source.records, origin: query.origin)\n                guard !cookies.isEmpty else {\n                    log(\"\\(source.label) produced 0 HTTPCookies.\")\n                    continue\n                }\n\n                diagnostics.foundAnyCookies = true\n                log(\"Loaded \\(cookies.count) cookies from \\(source.label) (\\(self.cookieSummary(cookies)))\")\n                let candidate = Candidate(label: source.label, cookies: cookies)\n                if let match = await self.applyCandidate(\n                    candidate,\n                    targetEmail: targetEmail,\n                    allowAnyAccount: allowAnyAccount,\n                    log: log,\n                    diagnostics: &diagnostics)\n                {\n                    return match\n                }\n            }\n            return nil\n        } catch let error as BrowserCookieError {\n            BrowserCookieAccessGate.recordIfNeeded(error)\n            if let hint = error.accessDeniedHint {\n                diagnostics.accessDeniedHints.append(hint)\n            }\n            log(\"Safari cookie load failed: \\(error.localizedDescription)\")\n            return nil\n        } catch {\n            log(\"Safari cookie load failed: \\(error.localizedDescription)\")\n            return nil\n        }\n    }\n\n    private func tryChrome(\n        targetEmail: String?,\n        allowAnyAccount: Bool,\n        log: @escaping (String) -> Void,\n        diagnostics: inout ImportDiagnostics) async -> ImportResult?\n    {\n        // Chrome fallback: may trigger Keychain prompt. Only do this if Safari didn't match.\n        do {\n            let query = BrowserCookieQuery(domains: Self.cookieDomains)\n            let chromeSources = try Self.cookieClient.records(\n                matching: query,\n                in: .chrome)\n            for source in chromeSources {\n                let cookies = BrowserCookieClient.makeHTTPCookies(source.records, origin: query.origin)\n                if cookies.isEmpty {\n                    log(\"\\(source.label) produced 0 HTTPCookies.\")\n                    continue\n                }\n                diagnostics.foundAnyCookies = true\n                log(\"Loaded \\(cookies.count) cookies from \\(source.label) (\\(self.cookieSummary(cookies)))\")\n                let candidate = Candidate(label: source.label, cookies: cookies)\n                if let match = await self.applyCandidate(\n                    candidate,\n                    targetEmail: targetEmail,\n                    allowAnyAccount: allowAnyAccount,\n                    log: log,\n                    diagnostics: &diagnostics)\n                {\n                    return match\n                }\n            }\n            return nil\n        } catch let error as BrowserCookieError {\n            BrowserCookieAccessGate.recordIfNeeded(error)\n            if let hint = error.accessDeniedHint {\n                diagnostics.accessDeniedHints.append(hint)\n            }\n            log(\"Chrome cookie load failed: \\(error.localizedDescription)\")\n            return nil\n        } catch {\n            log(\"Chrome cookie load failed: \\(error.localizedDescription)\")\n            return nil\n        }\n    }\n\n    private func tryFirefox(\n        targetEmail: String?,\n        allowAnyAccount: Bool,\n        log: @escaping (String) -> Void,\n        diagnostics: inout ImportDiagnostics) async -> ImportResult?\n    {\n        // Firefox fallback: no Keychain, but still only after Safari/Chrome.\n        do {\n            let query = BrowserCookieQuery(domains: Self.cookieDomains)\n            let firefoxSources = try Self.cookieClient.records(\n                matching: query,\n                in: .firefox)\n            for source in firefoxSources {\n                let cookies = BrowserCookieClient.makeHTTPCookies(source.records, origin: query.origin)\n                if cookies.isEmpty {\n                    log(\"\\(source.label) produced 0 HTTPCookies.\")\n                    continue\n                }\n                diagnostics.foundAnyCookies = true\n                log(\"Loaded \\(cookies.count) cookies from \\(source.label) (\\(self.cookieSummary(cookies)))\")\n                let candidate = Candidate(label: source.label, cookies: cookies)\n                if let match = await self.applyCandidate(\n                    candidate,\n                    targetEmail: targetEmail,\n                    allowAnyAccount: allowAnyAccount,\n                    log: log,\n                    diagnostics: &diagnostics)\n                {\n                    return match\n                }\n            }\n            return nil\n        } catch let error as BrowserCookieError {\n            BrowserCookieAccessGate.recordIfNeeded(error)\n            if let hint = error.accessDeniedHint {\n                diagnostics.accessDeniedHints.append(hint)\n            }\n            log(\"Firefox cookie load failed: \\(error.localizedDescription)\")\n            return nil\n        } catch {\n            log(\"Firefox cookie load failed: \\(error.localizedDescription)\")\n            return nil\n        }\n    }\n\n    private func trySource(\n        _ source: Browser,\n        targetEmail: String?,\n        allowAnyAccount: Bool,\n        log: @escaping (String) -> Void,\n        diagnostics: inout ImportDiagnostics) async -> ImportResult?\n    {\n        switch source {\n        case .safari:\n            await self.trySafari(\n                targetEmail: targetEmail,\n                allowAnyAccount: allowAnyAccount,\n                log: log,\n                diagnostics: &diagnostics)\n        case .chrome:\n            await self.tryChrome(\n                targetEmail: targetEmail,\n                allowAnyAccount: allowAnyAccount,\n                log: log,\n                diagnostics: &diagnostics)\n        case .firefox:\n            await self.tryFirefox(\n                targetEmail: targetEmail,\n                allowAnyAccount: allowAnyAccount,\n                log: log,\n                diagnostics: &diagnostics)\n        default:\n            nil\n        }\n    }\n\n    private func applyCandidate(\n        _ candidate: Candidate,\n        targetEmail: String?,\n        allowAnyAccount: Bool,\n        log: @escaping (String) -> Void,\n        diagnostics: inout ImportDiagnostics) async -> ImportResult?\n    {\n        switch await self.evaluateCandidate(\n            candidate,\n            targetEmail: targetEmail,\n            allowAnyAccount: allowAnyAccount,\n            log: log)\n        {\n        case let .match(candidate, signedInEmail):\n            log(\"Selected \\(candidate.label) (matches Codex: \\(signedInEmail))\")\n            guard let targetEmail else { return nil }\n            if let result = try? await self.persist(candidate: candidate, targetEmail: targetEmail, logger: log) {\n                self.cacheCookies(candidate: candidate)\n                return result\n            }\n            return nil\n        case let .mismatch(candidate, signedInEmail):\n            await self.handleMismatch(\n                candidate: candidate,\n                signedInEmail: signedInEmail,\n                log: log,\n                diagnostics: &diagnostics)\n            return nil\n        case let .loggedIn(candidate, signedInEmail):\n            log(\"Selected \\(candidate.label) (signed in: \\(signedInEmail))\")\n            if let result = try? await self.persist(candidate: candidate, targetEmail: signedInEmail, logger: log) {\n                self.cacheCookies(candidate: candidate)\n                return result\n            }\n            return nil\n        case .unknown:\n            if allowAnyAccount {\n                log(\"Selected \\(candidate.label) (signed in: unknown)\")\n                if let result = try? await self.persistToDefaultStore(candidate: candidate, logger: log) {\n                    self.cacheCookies(candidate: candidate)\n                    return result\n                }\n                return nil\n            }\n            diagnostics.foundUnknownEmail = true\n            return nil\n        case .loginRequired:\n            return nil\n        }\n    }\n\n    private func evaluateCandidate(\n        _ candidate: Candidate,\n        targetEmail: String?,\n        allowAnyAccount: Bool,\n        log: @escaping (String) -> Void) async -> CandidateEvaluation\n    {\n        log(\"Trying candidate \\(candidate.label) (\\(candidate.cookies.count) cookies)\")\n\n        let apiEmail = await self.fetchSignedInEmailFromAPI(cookies: candidate.cookies, logger: log)\n        if let apiEmail {\n            log(\"Candidate \\(candidate.label) API email: \\(apiEmail)\")\n        }\n\n        // Prefer the API email when available (fast; avoids WebKit hydration/timeout risks).\n        if let apiEmail, !apiEmail.isEmpty {\n            if let targetEmail {\n                if apiEmail.lowercased() == targetEmail.lowercased() {\n                    return .match(candidate: candidate, signedInEmail: apiEmail)\n                }\n                return .mismatch(candidate: candidate, signedInEmail: apiEmail)\n            }\n            if allowAnyAccount { return .loggedIn(candidate: candidate, signedInEmail: apiEmail) }\n        }\n\n        if !self.hasSessionCookies(candidate.cookies) {\n            log(\"Candidate \\(candidate.label) missing session cookies; skipping\")\n            return .loginRequired(candidate: candidate)\n        }\n\n        let scratch = WKWebsiteDataStore.nonPersistent()\n        await self.setCookies(candidate.cookies, into: scratch)\n\n        do {\n            let probe = try await OpenAIDashboardFetcher().probeUsagePage(\n                websiteDataStore: scratch,\n                logger: log,\n                timeout: 25)\n            let signedInEmail = probe.signedInEmail?.trimmingCharacters(in: .whitespacesAndNewlines)\n            log(\"Candidate \\(candidate.label) DOM email: \\(signedInEmail ?? \"unknown\")\")\n\n            let resolvedEmail = signedInEmail?.trimmingCharacters(in: .whitespacesAndNewlines)\n            if let resolvedEmail, !resolvedEmail.isEmpty {\n                if let targetEmail {\n                    if resolvedEmail.lowercased() == targetEmail.lowercased() {\n                        return .match(candidate: candidate, signedInEmail: resolvedEmail)\n                    }\n                    return .mismatch(candidate: candidate, signedInEmail: resolvedEmail)\n                }\n                if allowAnyAccount { return .loggedIn(candidate: candidate, signedInEmail: resolvedEmail) }\n            }\n\n            return .unknown(candidate: candidate)\n        } catch OpenAIDashboardFetcher.FetchError.loginRequired {\n            log(\"Candidate \\(candidate.label) requires login.\")\n            return .loginRequired(candidate: candidate)\n        } catch {\n            log(\"Candidate \\(candidate.label) probe error: \\(error.localizedDescription)\")\n            return .unknown(candidate: candidate)\n        }\n    }\n\n    private func hasSessionCookies(_ cookies: [HTTPCookie]) -> Bool {\n        for cookie in cookies {\n            let name = cookie.name.lowercased()\n            if name.contains(\"session-token\") || name.contains(\"authjs\") || name.contains(\"next-auth\") {\n                return true\n            }\n            if name == \"_account\" { return true }\n        }\n        return false\n    }\n\n    private func handleMismatch(\n        candidate: Candidate,\n        signedInEmail: String,\n        log: @escaping (String) -> Void,\n        diagnostics: inout ImportDiagnostics) async\n    {\n        log(\"Candidate \\(candidate.label) mismatch (\\(signedInEmail)); continuing browser search\")\n        diagnostics.mismatches.append(FoundAccount(sourceLabel: candidate.label, email: signedInEmail))\n        // Mismatch still means we found a valid signed-in session. Persist it keyed by its email so if\n        // the user switches Codex accounts later, we can reuse this session immediately without another\n        // Keychain prompt.\n        await self.persistCookies(candidate: candidate, accountEmail: signedInEmail, logger: log)\n    }\n\n    private func fetchSignedInEmailFromAPI(\n        cookies: [HTTPCookie],\n        logger: (String) -> Void) async -> String?\n    {\n        let chatgptCookies = cookies.filter { $0.domain.lowercased().contains(\"chatgpt.com\") }\n        guard !chatgptCookies.isEmpty else { return nil }\n\n        let cookieHeader = chatgptCookies\n            .map { \"\\($0.name)=\\($0.value)\" }\n            .joined(separator: \"; \")\n\n        let endpoints = [\n            \"https://chatgpt.com/backend-api/me\",\n            \"https://chatgpt.com/api/auth/session\",\n        ]\n\n        for urlString in endpoints {\n            guard let url = URL(string: urlString) else { continue }\n            var request = URLRequest(url: url)\n            request.httpMethod = \"GET\"\n            request.timeoutInterval = 10\n            request.setValue(cookieHeader, forHTTPHeaderField: \"Cookie\")\n            request.setValue(\"application/json\", forHTTPHeaderField: \"Accept\")\n\n            do {\n                let (data, response) = try await URLSession.shared.data(for: request)\n                let status = (response as? HTTPURLResponse)?.statusCode ?? -1\n                logger(\"API \\(url.host ?? \"chatgpt.com\") \\(url.path) status=\\(status)\")\n                guard status >= 200, status < 300 else { continue }\n                if let email = Self.findFirstEmail(inJSONData: data) {\n                    return email.trimmingCharacters(in: .whitespacesAndNewlines)\n                }\n            } catch {\n                logger(\"API request failed: \\(error.localizedDescription)\")\n            }\n        }\n\n        return nil\n    }\n\n    private static func findFirstEmail(inJSONData data: Data) -> String? {\n        guard let json = try? JSONSerialization.jsonObject(with: data, options: []) else { return nil }\n        var queue: [Any] = [json]\n        var seen = 0\n        while !queue.isEmpty, seen < 2000 {\n            let cur = queue.removeFirst()\n            seen += 1\n            if let str = cur as? String, str.contains(\"@\") {\n                return str\n            }\n            if let dict = cur as? [String: Any] {\n                for (k, v) in dict {\n                    if k.lowercased() == \"email\", let s = v as? String, s.contains(\"@\") { return s }\n                    queue.append(v)\n                }\n            } else if let arr = cur as? [Any] {\n                queue.append(contentsOf: arr)\n            }\n        }\n        return nil\n    }\n\n    private func persist(\n        candidate: Candidate,\n        targetEmail: String,\n        logger: @escaping (String) -> Void) async throws -> ImportResult\n    {\n        let persistent = OpenAIDashboardWebsiteDataStore.store(forAccountEmail: targetEmail)\n        await self.clearChatGPTCookies(in: persistent)\n        await self.setCookies(candidate.cookies, into: persistent)\n\n        // Validate against the persistent store (login + email sync).\n        do {\n            let probe = try await OpenAIDashboardFetcher().probeUsagePage(\n                websiteDataStore: persistent,\n                logger: logger,\n                timeout: 20)\n            let signed = probe.signedInEmail?.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)\n            let matches = signed?.lowercased() == targetEmail.lowercased()\n            logger(\"Persistent session signed in as: \\(signed ?? \"unknown\")\")\n            if signed != nil, matches == false {\n                let found = signed?.isEmpty == false\n                    ? [FoundAccount(sourceLabel: candidate.label, email: signed!)]\n                    : []\n                throw ImportError.noMatchingAccount(found: found)\n            }\n            return ImportResult(\n                sourceLabel: candidate.label,\n                cookieCount: candidate.cookies.count,\n                signedInEmail: signed,\n                matchesCodexEmail: matches)\n        } catch OpenAIDashboardFetcher.FetchError.loginRequired {\n            logger(\"Selected \\(candidate.label) but dashboard still requires login.\")\n            throw ImportError.dashboardStillRequiresLogin\n        }\n    }\n\n    private func persistToDefaultStore(\n        candidate: Candidate,\n        logger: @escaping (String) -> Void) async throws -> ImportResult\n    {\n        let persistent = OpenAIDashboardWebsiteDataStore.store(forAccountEmail: nil)\n        await self.clearChatGPTCookies(in: persistent)\n        await self.setCookies(candidate.cookies, into: persistent)\n\n        do {\n            let probe = try await OpenAIDashboardFetcher().probeUsagePage(\n                websiteDataStore: persistent,\n                logger: logger,\n                timeout: 20)\n            let signed = probe.signedInEmail?.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)\n            logger(\"Persistent session signed in as: \\(signed ?? \"unknown\")\")\n            return ImportResult(\n                sourceLabel: candidate.label,\n                cookieCount: candidate.cookies.count,\n                signedInEmail: signed,\n                matchesCodexEmail: false)\n        } catch OpenAIDashboardFetcher.FetchError.loginRequired {\n            logger(\"Selected \\(candidate.label) but dashboard still requires login.\")\n            throw ImportError.dashboardStillRequiresLogin\n        }\n    }\n\n    // MARK: - Candidates\n\n    private func cookies(from pairs: [(name: String, value: String)]) -> [HTTPCookie] {\n        var cookies: [HTTPCookie] = []\n        for domain in Self.cookieDomains {\n            for pair in pairs {\n                let props: [HTTPCookiePropertyKey: Any] = [\n                    .name: pair.name,\n                    .value: pair.value,\n                    .domain: domain,\n                    .path: \"/\",\n                    .secure: true,\n                ]\n                if let cookie = HTTPCookie(properties: props) {\n                    cookies.append(cookie)\n                }\n            }\n        }\n        return cookies\n    }\n\n    private func cacheCookies(candidate: Candidate) {\n        let header = self.cookieHeader(from: candidate.cookies)\n        guard !header.isEmpty else { return }\n        CookieHeaderCache.store(provider: .codex, cookieHeader: header, sourceLabel: candidate.label)\n    }\n\n    private func cookieHeader(from cookies: [HTTPCookie]) -> String {\n        cookies.map { \"\\($0.name)=\\($0.value)\" }.joined(separator: \"; \")\n    }\n\n    private struct Candidate {\n        let label: String\n        let cookies: [HTTPCookie]\n    }\n\n    // MARK: - WebKit cookie store\n\n    private func persistCookies(candidate: Candidate, accountEmail: String, logger: (String) -> Void) async {\n        let store = OpenAIDashboardWebsiteDataStore.store(forAccountEmail: accountEmail)\n        await self.clearChatGPTCookies(in: store)\n        await self.setCookies(candidate.cookies, into: store)\n        logger(\"Persisted cookies for \\(accountEmail) (source=\\(candidate.label))\")\n    }\n\n    private func clearChatGPTCookies(in store: WKWebsiteDataStore) async {\n        await withCheckedContinuation { cont in\n            store.fetchDataRecords(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes()) { records in\n                let filtered = records.filter { record in\n                    let name = record.displayName.lowercased()\n                    return name.contains(\"chatgpt.com\") || name.contains(\"openai.com\")\n                }\n                store.removeData(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes(), for: filtered) {\n                    cont.resume()\n                }\n            }\n        }\n    }\n\n    private func setCookies(_ cookies: [HTTPCookie], into store: WKWebsiteDataStore) async {\n        for cookie in cookies {\n            await withCheckedContinuation { cont in\n                store.httpCookieStore.setCookie(cookie) { cont.resume() }\n            }\n        }\n    }\n\n    private func cookieSummary(_ cookies: [HTTPCookie]) -> String {\n        let nameCounts = Dictionary(grouping: cookies, by: \\.name).mapValues { $0.count }\n        let important = [\n            \"__Secure-next-auth.session-token\",\n            \"__Secure-next-auth.session-token.0\",\n            \"__Secure-next-auth.session-token.1\",\n            \"_account\",\n            \"oai-did\",\n            \"cf_clearance\",\n        ]\n        let parts: [String] = important.compactMap { name -> String? in\n            guard let c = nameCounts[name], c > 0 else { return nil }\n            return \"\\(name)=\\(c)\"\n        }\n        if parts.isEmpty { return \"no key cookies detected\" }\n        return parts.joined(separator: \", \")\n    }\n}\n#else\nimport Foundation\n\n@MainActor\npublic struct OpenAIDashboardBrowserCookieImporter {\n    public struct FoundAccount: Sendable, Hashable {\n        public let sourceLabel: String\n        public let email: String\n\n        public init(sourceLabel: String, email: String) {\n            self.sourceLabel = sourceLabel\n            self.email = email\n        }\n    }\n\n    public enum ImportError: LocalizedError {\n        case noCookiesFound\n        case browserAccessDenied(details: String)\n        case dashboardStillRequiresLogin\n        case noMatchingAccount(found: [FoundAccount])\n        case manualCookieHeaderInvalid\n\n        public var errorDescription: String? {\n            switch self {\n            case .noCookiesFound:\n                return \"No browser cookies found.\"\n            case let .browserAccessDenied(details):\n                return \"Browser cookie access denied. \\(details)\"\n            case .dashboardStillRequiresLogin:\n                return \"Browser cookies imported, but dashboard still requires login.\"\n            case let .noMatchingAccount(found):\n                if found.isEmpty { return \"No matching OpenAI web session found in browsers.\" }\n                let display = found\n                    .sorted { lhs, rhs in\n                        if lhs.sourceLabel == rhs.sourceLabel { return lhs.email < rhs.email }\n                        return lhs.sourceLabel < rhs.sourceLabel\n                    }\n                    .map { \"\\($0.sourceLabel)=\\($0.email)\" }\n                    .joined(separator: \", \")\n                return \"OpenAI web session does not match Codex account. Found: \\(display).\"\n            case .manualCookieHeaderInvalid:\n                return \"Manual cookie header is missing a valid OpenAI session cookie.\"\n            }\n        }\n    }\n\n    public struct ImportResult: Sendable {\n        public let sourceLabel: String\n        public let cookieCount: Int\n        public let signedInEmail: String?\n        public let matchesCodexEmail: Bool\n\n        public init(sourceLabel: String, cookieCount: Int, signedInEmail: String?, matchesCodexEmail: Bool) {\n            self.sourceLabel = sourceLabel\n            self.cookieCount = cookieCount\n            self.signedInEmail = signedInEmail\n            self.matchesCodexEmail = matchesCodexEmail\n        }\n    }\n\n    public init() {}\n\n    public func importBestCookies(\n        intoAccountEmail _: String?,\n        allowAnyAccount _: Bool = false,\n        logger _: ((String) -> Void)? = nil) async throws -> ImportResult\n    {\n        throw ImportError.browserAccessDenied(details: \"OpenAI web cookie import is only supported on macOS.\")\n    }\n\n    public func importManualCookies(\n        cookieHeader _: String,\n        intoAccountEmail _: String?,\n        allowAnyAccount _: Bool = false,\n        logger _: ((String) -> Void)? = nil) async throws -> ImportResult\n    {\n        throw ImportError.browserAccessDenied(details: \"OpenAI web cookie import is only supported on macOS.\")\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/CodexBarCore/OpenAIWeb/OpenAIDashboardFetcher.swift",
    "content": "#if os(macOS)\nimport CoreGraphics\nimport Foundation\nimport WebKit\n\n@MainActor\npublic struct OpenAIDashboardFetcher {\n    public enum FetchError: LocalizedError {\n        case loginRequired\n        case noDashboardData(body: String)\n\n        public var errorDescription: String? {\n            switch self {\n            case .loginRequired:\n                \"OpenAI web access requires login.\"\n            case let .noDashboardData(body):\n                \"OpenAI dashboard data not found. Body sample: \\(body.prefix(200))\"\n            }\n        }\n    }\n\n    private let usageURL = URL(string: \"https://chatgpt.com/codex/settings/usage\")!\n\n    public init() {}\n\n    public nonisolated static func offscreenHostWindowFrame(for visibleFrame: CGRect) -> CGRect {\n        let width: CGFloat = min(1200, visibleFrame.width)\n        let height: CGFloat = min(1600, visibleFrame.height)\n\n        // Keep the WebView \"visible\" for WebKit hydration, but never show it to the user.\n        // Place the window almost entirely off-screen; leave only a 1×1 px intersection.\n        let sliver: CGFloat = 1\n        return CGRect(\n            x: visibleFrame.maxX - sliver,\n            y: visibleFrame.maxY - sliver,\n            width: width,\n            height: height)\n    }\n\n    public nonisolated static func offscreenHostAlphaValue() -> CGFloat {\n        // Must be > 0 or WebKit can throttle hydration/timers on the Codex usage SPA.\n        0.001\n    }\n\n    public struct ProbeResult: Sendable {\n        public let href: String?\n        public let loginRequired: Bool\n        public let workspacePicker: Bool\n        public let cloudflareInterstitial: Bool\n        public let signedInEmail: String?\n        public let bodyText: String?\n\n        public init(\n            href: String?,\n            loginRequired: Bool,\n            workspacePicker: Bool,\n            cloudflareInterstitial: Bool,\n            signedInEmail: String?,\n            bodyText: String?)\n        {\n            self.href = href\n            self.loginRequired = loginRequired\n            self.workspacePicker = workspacePicker\n            self.cloudflareInterstitial = cloudflareInterstitial\n            self.signedInEmail = signedInEmail\n            self.bodyText = bodyText\n        }\n    }\n\n    public func loadLatestDashboard(\n        accountEmail: String?,\n        logger: ((String) -> Void)? = nil,\n        debugDumpHTML: Bool = false,\n        timeout: TimeInterval = 60) async throws -> OpenAIDashboardSnapshot\n    {\n        let store = OpenAIDashboardWebsiteDataStore.store(forAccountEmail: accountEmail)\n        return try await self.loadLatestDashboard(\n            websiteDataStore: store,\n            logger: logger,\n            debugDumpHTML: debugDumpHTML,\n            timeout: timeout)\n    }\n\n    public func loadLatestDashboard(\n        websiteDataStore: WKWebsiteDataStore,\n        logger: ((String) -> Void)? = nil,\n        debugDumpHTML: Bool = false,\n        timeout: TimeInterval = 60) async throws -> OpenAIDashboardSnapshot\n    {\n        let deadline = Self.deadline(startingAt: Date(), timeout: timeout)\n        let lease = try await self.makeWebView(\n            websiteDataStore: websiteDataStore,\n            logger: logger,\n            timeout: Self.remainingTimeout(until: deadline))\n        defer { lease.release() }\n        let webView = lease.webView\n        let log = lease.log\n\n        var lastBody: String?\n        var lastHTML: String?\n        var lastHref: String?\n        var lastFlags: (loginRequired: Bool, workspacePicker: Bool, cloudflare: Bool)?\n        var codeReviewFirstSeenAt: Date?\n        var anyDashboardSignalAt: Date?\n        var creditsHeaderVisibleAt: Date?\n        var lastUsageBreakdownDebug: String?\n        var lastCreditsPurchaseURL: String?\n\n        while Date() < deadline {\n            let scrape = try await self.scrape(webView: webView)\n            lastBody = scrape.bodyText ?? lastBody\n            lastHTML = scrape.bodyHTML ?? lastHTML\n\n            if scrape.href != lastHref\n                || lastFlags?.loginRequired != scrape.loginRequired\n                || lastFlags?.workspacePicker != scrape.workspacePicker\n                || lastFlags?.cloudflare != scrape.cloudflareInterstitial\n            {\n                lastHref = scrape.href\n                lastFlags = (scrape.loginRequired, scrape.workspacePicker, scrape.cloudflareInterstitial)\n                let href = scrape.href ?? \"nil\"\n                log(\n                    \"href=\\(href) login=\\(scrape.loginRequired) \" +\n                        \"workspace=\\(scrape.workspacePicker) cloudflare=\\(scrape.cloudflareInterstitial)\")\n            }\n\n            if scrape.workspacePicker {\n                try? await Task.sleep(for: .milliseconds(500))\n                continue\n            }\n\n            // The page is a SPA and can land on ChatGPT UI or other routes; keep forcing the usage URL.\n            if let href = scrape.href, !href.contains(\"/codex/settings/usage\") {\n                _ = webView.load(URLRequest(url: self.usageURL))\n                try? await Task.sleep(for: .milliseconds(500))\n                continue\n            }\n\n            if scrape.loginRequired {\n                if debugDumpHTML, let html = scrape.bodyHTML {\n                    Self.writeDebugArtifacts(html: html, bodyText: scrape.bodyText, logger: log)\n                }\n                throw FetchError.loginRequired\n            }\n\n            if scrape.cloudflareInterstitial {\n                if debugDumpHTML, let html = scrape.bodyHTML {\n                    Self.writeDebugArtifacts(html: html, bodyText: scrape.bodyText, logger: log)\n                }\n                throw FetchError.noDashboardData(body: \"Cloudflare challenge detected in WebView.\")\n            }\n\n            let bodyText = scrape.bodyText ?? \"\"\n            let codeReview = OpenAIDashboardParser.parseCodeReviewRemainingPercent(bodyText: bodyText)\n            let events = OpenAIDashboardParser.parseCreditEvents(rows: scrape.rows)\n            let breakdown = OpenAIDashboardSnapshot.makeDailyBreakdown(from: events, maxDays: 30)\n            let usageBreakdown = scrape.usageBreakdown\n            let rateLimits = OpenAIDashboardParser.parseRateLimits(bodyText: bodyText)\n            let creditsRemaining = OpenAIDashboardParser.parseCreditsRemaining(bodyText: bodyText)\n            let accountPlan = scrape.bodyHTML.flatMap(OpenAIDashboardParser.parsePlanFromHTML)\n            let hasUsageLimits = rateLimits.primary != nil || rateLimits.secondary != nil\n\n            if codeReview != nil, codeReviewFirstSeenAt == nil { codeReviewFirstSeenAt = Date() }\n            if anyDashboardSignalAt == nil,\n               codeReview != nil || !usageBreakdown.isEmpty || scrape.creditsHeaderPresent ||\n               hasUsageLimits || creditsRemaining != nil\n            {\n                anyDashboardSignalAt = Date()\n            }\n            if codeReview != nil, usageBreakdown.isEmpty,\n               let debug = scrape.usageBreakdownDebug, !debug.isEmpty,\n               debug != lastUsageBreakdownDebug\n            {\n                lastUsageBreakdownDebug = debug\n                log(\"usage breakdown debug: \\(debug)\")\n            }\n            if let purchaseURL = scrape.creditsPurchaseURL, purchaseURL != lastCreditsPurchaseURL {\n                lastCreditsPurchaseURL = purchaseURL\n                log(\"credits purchase url: \\(purchaseURL)\")\n            }\n            if events.isEmpty,\n               codeReview != nil || !usageBreakdown.isEmpty || hasUsageLimits || creditsRemaining != nil\n            {\n                log(\n                    \"credits header present=\\(scrape.creditsHeaderPresent) \" +\n                        \"inViewport=\\(scrape.creditsHeaderInViewport) didScroll=\\(scrape.didScrollToCredits) \" +\n                        \"rows=\\(scrape.rows.count)\")\n                if scrape.didScrollToCredits {\n                    log(\"scrollIntoView(Credits usage history) requested; waiting…\")\n                    try? await Task.sleep(for: .milliseconds(600))\n                    continue\n                }\n\n                // Avoid returning early when the usage breakdown chart hydrates before the (often virtualized)\n                // credits table. When we detect a dashboard signal, give credits history a moment to appear.\n                if scrape.creditsHeaderPresent, scrape.creditsHeaderInViewport, creditsHeaderVisibleAt == nil {\n                    creditsHeaderVisibleAt = Date()\n                }\n                if Self.shouldWaitForCreditsHistory(.init(\n                    now: Date(),\n                    anyDashboardSignalAt: anyDashboardSignalAt,\n                    creditsHeaderVisibleAt: creditsHeaderVisibleAt,\n                    creditsHeaderPresent: scrape.creditsHeaderPresent,\n                    creditsHeaderInViewport: scrape.creditsHeaderInViewport,\n                    didScrollToCredits: scrape.didScrollToCredits))\n                {\n                    try? await Task.sleep(for: .milliseconds(400))\n                    continue\n                }\n            }\n\n            if codeReview != nil || !events.isEmpty || !usageBreakdown\n                .isEmpty || hasUsageLimits || creditsRemaining != nil\n            {\n                // The usage breakdown chart is hydrated asynchronously. When code review is already present,\n                // give it a moment to populate so the menu can show it.\n                if codeReview != nil, usageBreakdown.isEmpty {\n                    let elapsed = Date().timeIntervalSince(codeReviewFirstSeenAt ?? Date())\n                    if elapsed < 6 {\n                        try? await Task.sleep(for: .milliseconds(400))\n                        continue\n                    }\n                }\n                return OpenAIDashboardSnapshot(\n                    signedInEmail: scrape.signedInEmail,\n                    codeReviewRemainingPercent: codeReview,\n                    creditEvents: events,\n                    dailyBreakdown: breakdown,\n                    usageBreakdown: usageBreakdown,\n                    creditsPurchaseURL: scrape.creditsPurchaseURL,\n                    primaryLimit: rateLimits.primary,\n                    secondaryLimit: rateLimits.secondary,\n                    creditsRemaining: creditsRemaining,\n                    accountPlan: accountPlan,\n                    updatedAt: Date())\n            }\n\n            try? await Task.sleep(for: .milliseconds(500))\n        }\n\n        if debugDumpHTML, let html = lastHTML {\n            Self.writeDebugArtifacts(html: html, bodyText: lastBody, logger: log)\n        }\n        throw FetchError.noDashboardData(body: lastBody ?? \"\")\n    }\n\n    struct CreditsHistoryWaitContext {\n        let now: Date\n        let anyDashboardSignalAt: Date?\n        let creditsHeaderVisibleAt: Date?\n        let creditsHeaderPresent: Bool\n        let creditsHeaderInViewport: Bool\n        let didScrollToCredits: Bool\n    }\n\n    nonisolated static func shouldWaitForCreditsHistory(_ context: CreditsHistoryWaitContext) -> Bool {\n        if context.didScrollToCredits { return true }\n\n        // When the header is visible but rows are still empty, wait briefly for the table to render.\n        if context.creditsHeaderPresent, context.creditsHeaderInViewport {\n            if let creditsHeaderVisibleAt = context.creditsHeaderVisibleAt {\n                return context.now.timeIntervalSince(creditsHeaderVisibleAt) < 2.5\n            }\n            return true\n        }\n\n        // Header not in view yet: allow a short grace period after we first detect any dashboard signal so\n        // a scroll (or hydration) can bring the credits section into the DOM.\n        if let anyDashboardSignalAt = context.anyDashboardSignalAt {\n            return context.now.timeIntervalSince(anyDashboardSignalAt) < 6.5\n        }\n        return false\n    }\n\n    public func clearSessionData(accountEmail: String?) async {\n        let store = OpenAIDashboardWebsiteDataStore.store(forAccountEmail: accountEmail)\n        OpenAIDashboardWebViewCache.shared.evict(websiteDataStore: store)\n        await OpenAIDashboardWebsiteDataStore.clearStore(forAccountEmail: accountEmail)\n    }\n\n    public func probeUsagePage(\n        websiteDataStore: WKWebsiteDataStore,\n        logger: ((String) -> Void)? = nil,\n        timeout: TimeInterval = 30) async throws -> ProbeResult\n    {\n        let deadline = Self.deadline(startingAt: Date(), timeout: timeout)\n        let lease = try await self.makeWebView(\n            websiteDataStore: websiteDataStore,\n            logger: logger,\n            timeout: Self.remainingTimeout(until: deadline))\n        defer { lease.release() }\n        let webView = lease.webView\n        let log = lease.log\n\n        var lastBody: String?\n        var lastHref: String?\n\n        while Date() < deadline {\n            let scrape = try await self.scrape(webView: webView)\n            lastBody = scrape.bodyText ?? lastBody\n            lastHref = scrape.href ?? lastHref\n\n            if scrape.workspacePicker {\n                try? await Task.sleep(for: .milliseconds(500))\n                continue\n            }\n\n            if let href = scrape.href, !href.contains(\"/codex/settings/usage\") {\n                _ = webView.load(URLRequest(url: self.usageURL))\n                try? await Task.sleep(for: .milliseconds(500))\n                continue\n            }\n\n            if scrape.loginRequired { throw FetchError.loginRequired }\n            if scrape.cloudflareInterstitial {\n                throw FetchError.noDashboardData(body: \"Cloudflare challenge detected in WebView.\")\n            }\n\n            return ProbeResult(\n                href: scrape.href,\n                loginRequired: scrape.loginRequired,\n                workspacePicker: scrape.workspacePicker,\n                cloudflareInterstitial: scrape.cloudflareInterstitial,\n                signedInEmail: scrape.signedInEmail?.trimmingCharacters(in: .whitespacesAndNewlines),\n                bodyText: scrape.bodyText)\n        }\n\n        log(\"Probe timed out (href=\\(lastHref ?? \"nil\"))\")\n        return ProbeResult(\n            href: lastHref,\n            loginRequired: false,\n            workspacePicker: false,\n            cloudflareInterstitial: false,\n            signedInEmail: nil,\n            bodyText: lastBody)\n    }\n\n    // MARK: - JS scrape\n\n    private struct ScrapeResult {\n        let loginRequired: Bool\n        let workspacePicker: Bool\n        let cloudflareInterstitial: Bool\n        let href: String?\n        let bodyText: String?\n        let bodyHTML: String?\n        let signedInEmail: String?\n        let creditsPurchaseURL: String?\n        let rows: [[String]]\n        let usageBreakdown: [OpenAIDashboardDailyBreakdown]\n        let usageBreakdownDebug: String?\n        let scrollY: Double\n        let scrollHeight: Double\n        let viewportHeight: Double\n        let creditsHeaderPresent: Bool\n        let creditsHeaderInViewport: Bool\n        let didScrollToCredits: Bool\n    }\n\n    private func scrape(webView: WKWebView) async throws -> ScrapeResult {\n        let any = try await webView.evaluateJavaScript(openAIDashboardScrapeScript)\n        guard let dict = any as? [String: Any] else {\n            return ScrapeResult(\n                loginRequired: true,\n                workspacePicker: false,\n                cloudflareInterstitial: false,\n                href: nil,\n                bodyText: nil,\n                bodyHTML: nil,\n                signedInEmail: nil,\n                creditsPurchaseURL: nil,\n                rows: [],\n                usageBreakdown: [],\n                usageBreakdownDebug: nil,\n                scrollY: 0,\n                scrollHeight: 0,\n                viewportHeight: 0,\n                creditsHeaderPresent: false,\n                creditsHeaderInViewport: false,\n                didScrollToCredits: false)\n        }\n\n        var loginRequired = (dict[\"loginRequired\"] as? Bool) ?? false\n        let workspacePicker = (dict[\"workspacePicker\"] as? Bool) ?? false\n        let cloudflareInterstitial = (dict[\"cloudflareInterstitial\"] as? Bool) ?? false\n        let rows = (dict[\"rows\"] as? [[String]]) ?? []\n        let bodyHTML = dict[\"bodyHTML\"] as? String\n\n        var usageBreakdown: [OpenAIDashboardDailyBreakdown] = []\n        let usageBreakdownDebug = dict[\"usageBreakdownDebug\"] as? String\n        if let raw = dict[\"usageBreakdownJSON\"] as? String, !raw.isEmpty {\n            do {\n                let decoder = JSONDecoder()\n                usageBreakdown = try decoder.decode([OpenAIDashboardDailyBreakdown].self, from: Data(raw.utf8))\n            } catch {\n                // Best-effort parse; ignore errors to avoid blocking other dashboard data.\n                usageBreakdown = []\n            }\n        }\n\n        var signedInEmail = dict[\"signedInEmail\"] as? String\n        if let bodyHTML,\n           signedInEmail == nil || signedInEmail?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true\n        {\n            signedInEmail = OpenAIDashboardParser.parseSignedInEmailFromClientBootstrap(html: bodyHTML)\n        }\n\n        if let bodyHTML, let authStatus = OpenAIDashboardParser.parseAuthStatusFromClientBootstrap(html: bodyHTML) {\n            if authStatus.lowercased() != \"logged_in\" {\n                // When logged out, the SPA can render a generic landing shell without obvious auth inputs,\n                // so treat it as login-required and let the caller retry cookie import.\n                loginRequired = true\n            }\n        }\n\n        return ScrapeResult(\n            loginRequired: loginRequired,\n            workspacePicker: workspacePicker,\n            cloudflareInterstitial: cloudflareInterstitial,\n            href: dict[\"href\"] as? String,\n            bodyText: dict[\"bodyText\"] as? String,\n            bodyHTML: bodyHTML,\n            signedInEmail: signedInEmail,\n            creditsPurchaseURL: dict[\"creditsPurchaseURL\"] as? String,\n            rows: rows,\n            usageBreakdown: usageBreakdown,\n            usageBreakdownDebug: usageBreakdownDebug,\n            scrollY: (dict[\"scrollY\"] as? NSNumber)?.doubleValue ?? 0,\n            scrollHeight: (dict[\"scrollHeight\"] as? NSNumber)?.doubleValue ?? 0,\n            viewportHeight: (dict[\"viewportHeight\"] as? NSNumber)?.doubleValue ?? 0,\n            creditsHeaderPresent: (dict[\"creditsHeaderPresent\"] as? Bool) ?? false,\n            creditsHeaderInViewport: (dict[\"creditsHeaderInViewport\"] as? Bool) ?? false,\n            didScrollToCredits: (dict[\"didScrollToCredits\"] as? Bool) ?? false)\n    }\n\n    private func makeWebView(\n        websiteDataStore: WKWebsiteDataStore,\n        logger: ((String) -> Void)?,\n        timeout: TimeInterval) async throws -> OpenAIDashboardWebViewLease\n    {\n        try await OpenAIDashboardWebViewCache.shared.acquire(\n            websiteDataStore: websiteDataStore,\n            usageURL: self.usageURL,\n            logger: logger,\n            navigationTimeout: timeout)\n    }\n\n    nonisolated static func sanitizedTimeout(_ timeout: TimeInterval) -> TimeInterval {\n        guard timeout.isFinite, timeout > 0 else { return 1 }\n        return timeout\n    }\n\n    nonisolated static func deadline(startingAt start: Date, timeout: TimeInterval) -> Date {\n        start.addingTimeInterval(self.sanitizedTimeout(timeout))\n    }\n\n    nonisolated static func remainingTimeout(until deadline: Date, now: Date = Date()) -> TimeInterval {\n        max(0, deadline.timeIntervalSince(now))\n    }\n\n    private static func writeDebugArtifacts(html: String, bodyText: String?, logger: (String) -> Void) {\n        let stamp = Int(Date().timeIntervalSince1970)\n        let dir = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)\n        let htmlURL = dir.appendingPathComponent(\"codex-openai-dashboard-\\(stamp).html\")\n        do {\n            try html.write(to: htmlURL, atomically: true, encoding: .utf8)\n            logger(\"Dumped HTML: \\(htmlURL.path)\")\n        } catch {\n            logger(\"Failed to dump HTML: \\(error.localizedDescription)\")\n        }\n\n        if let bodyText, !bodyText.isEmpty {\n            let textURL = dir.appendingPathComponent(\"codex-openai-dashboard-\\(stamp).txt\")\n            do {\n                try bodyText.write(to: textURL, atomically: true, encoding: .utf8)\n                logger(\"Dumped text: \\(textURL.path)\")\n            } catch {\n                logger(\"Failed to dump text: \\(error.localizedDescription)\")\n            }\n        }\n    }\n}\n#else\nimport Foundation\n\n@MainActor\npublic struct OpenAIDashboardFetcher {\n    public enum FetchError: LocalizedError {\n        case loginRequired\n        case noDashboardData(body: String)\n\n        public var errorDescription: String? {\n            switch self {\n            case .loginRequired:\n                \"OpenAI web access requires login.\"\n            case let .noDashboardData(body):\n                \"OpenAI dashboard data not found. Body sample: \\(body.prefix(200))\"\n            }\n        }\n    }\n\n    public init() {}\n\n    public func loadLatestDashboard(\n        accountEmail _: String?,\n        logger _: ((String) -> Void)? = nil,\n        debugDumpHTML _: Bool = false,\n        timeout _: TimeInterval = 60) async throws -> OpenAIDashboardSnapshot\n    {\n        throw FetchError.noDashboardData(body: \"OpenAI web dashboard fetch is only supported on macOS.\")\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/CodexBarCore/OpenAIWeb/OpenAIDashboardNavigationDelegate.swift",
    "content": "#if os(macOS)\nimport Foundation\nimport WebKit\n\n// MARK: - Navigation helper (revived from the old credits scraper)\n\n@MainActor\nfinal class NavigationDelegate: NSObject, WKNavigationDelegate {\n    private let completion: (Result<Void, Error>) -> Void\n    private var hasCompleted: Bool = false\n    private var timeoutTask: Task<Void, Never>?\n    static var associationKey: UInt8 = 0\n\n    init(completion: @escaping (Result<Void, Error>) -> Void) {\n        self.completion = completion\n    }\n\n    func armTimeout(seconds: TimeInterval) {\n        self.timeoutTask?.cancel()\n        self.timeoutTask = Task { @MainActor [weak self] in\n            guard let self else { return }\n            let nanoseconds = UInt64(max(seconds, 0) * 1_000_000_000)\n            try? await Task.sleep(nanoseconds: nanoseconds)\n            self.completeOnce(.failure(URLError(.timedOut)))\n        }\n    }\n\n    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {\n        self.completeOnce(.success(()))\n    }\n\n    func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {\n        if Self.shouldIgnoreNavigationError(error) { return }\n        self.completeOnce(.failure(error))\n    }\n\n    func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {\n        if Self.shouldIgnoreNavigationError(error) { return }\n        self.completeOnce(.failure(error))\n    }\n\n    nonisolated static func shouldIgnoreNavigationError(_ error: Error) -> Bool {\n        let nsError = error as NSError\n        return nsError.domain == NSURLErrorDomain && nsError.code == NSURLErrorCancelled\n    }\n\n    private func completeOnce(_ result: Result<Void, Error>) {\n        guard !self.hasCompleted else { return }\n        self.hasCompleted = true\n        self.timeoutTask?.cancel()\n        self.timeoutTask = nil\n        self.completion(result)\n    }\n}\n\nextension WKWebView {\n    var codexNavigationDelegate: NavigationDelegate? {\n        get {\n            objc_getAssociatedObject(self, &NavigationDelegate.associationKey) as? NavigationDelegate\n        }\n        set {\n            objc_setAssociatedObject(\n                self,\n                &NavigationDelegate.associationKey,\n                newValue,\n                .OBJC_ASSOCIATION_RETAIN_NONATOMIC)\n        }\n    }\n}\n\n#endif\n"
  },
  {
    "path": "Sources/CodexBarCore/OpenAIWeb/OpenAIDashboardParser.swift",
    "content": "import Foundation\n\npublic enum OpenAIDashboardParser {\n    /// Extracts the signed-in email from the embedded `client-bootstrap` JSON payload, if present.\n    ///\n    /// The Codex usage dashboard currently ships a JSON blob in:\n    /// `<script type=\"application/json\" id=\"client-bootstrap\">…</script>`.\n    /// WebKit `document.body.innerText` often does not include the email, so we parse it from HTML.\n    public static func parseSignedInEmailFromClientBootstrap(html: String) -> String? {\n        guard let data = self.clientBootstrapJSONData(fromHTML: html) else { return nil }\n        guard let json = try? JSONSerialization.jsonObject(with: data, options: []) else { return nil }\n\n        // Fast path: common structure.\n        if let dict = json as? [String: Any] {\n            if let session = dict[\"session\"] as? [String: Any],\n               let user = session[\"user\"] as? [String: Any],\n               let email = user[\"email\"] as? String,\n               email.contains(\"@\")\n            {\n                return email.trimmingCharacters(in: .whitespacesAndNewlines)\n            }\n\n            if let user = dict[\"user\"] as? [String: Any],\n               let email = user[\"email\"] as? String,\n               email.contains(\"@\")\n            {\n                return email.trimmingCharacters(in: .whitespacesAndNewlines)\n            }\n        }\n\n        // Fallback: BFS scan for an email key/value.\n        var queue: [Any] = [json]\n        var seen = 0\n        while !queue.isEmpty, seen < 4000 {\n            let cur = queue.removeFirst()\n            seen += 1\n            if let dict = cur as? [String: Any] {\n                for (k, v) in dict {\n                    if k.lowercased() == \"email\", let email = v as? String, email.contains(\"@\") {\n                        return email.trimmingCharacters(in: .whitespacesAndNewlines)\n                    }\n                    queue.append(v)\n                }\n            } else if let arr = cur as? [Any] {\n                queue.append(contentsOf: arr)\n            }\n        }\n        return nil\n    }\n\n    /// Extracts the auth status from `client-bootstrap`, if present.\n    /// Expected values include `logged_in` and `logged_out`.\n    public static func parseAuthStatusFromClientBootstrap(html: String) -> String? {\n        guard let data = self.clientBootstrapJSONData(fromHTML: html) else { return nil }\n        guard let json = try? JSONSerialization.jsonObject(with: data, options: []) else { return nil }\n        guard let dict = json as? [String: Any] else { return nil }\n        if let authStatus = dict[\"authStatus\"] as? String, !authStatus.isEmpty {\n            return authStatus.trimmingCharacters(in: .whitespacesAndNewlines)\n        }\n        return nil\n    }\n\n    public static func parseCodeReviewRemainingPercent(bodyText: String) -> Double? {\n        let cleaned = bodyText.replacingOccurrences(of: \"\\r\", with: \"\\n\")\n        for regex in self.codeReviewRegexes {\n            let range = NSRange(cleaned.startIndex..<cleaned.endIndex, in: cleaned)\n            guard let match = regex.firstMatch(in: cleaned, options: [], range: range),\n                  match.numberOfRanges >= 2,\n                  let r = Range(match.range(at: 1), in: cleaned)\n            else { continue }\n            if let val = Double(cleaned[r]) { return min(100, max(0, val)) }\n        }\n        return nil\n    }\n\n    public static func parseCreditsRemaining(bodyText: String) -> Double? {\n        let cleaned = bodyText.replacingOccurrences(of: \"\\r\", with: \"\\n\")\n        let patterns = [\n            #\"credits\\s*remaining[^0-9]*([0-9][0-9.,]*)\"#,\n            #\"remaining\\s*credits[^0-9]*([0-9][0-9.,]*)\"#,\n            #\"credit\\s*balance[^0-9]*([0-9][0-9.,]*)\"#,\n        ]\n        for pattern in patterns {\n            if let val = TextParsing.firstNumber(pattern: pattern, text: cleaned) { return val }\n        }\n        return nil\n    }\n\n    public static func parseRateLimits(\n        bodyText: String,\n        now: Date = .init()) -> (primary: RateWindow?, secondary: RateWindow?)\n    {\n        let cleaned = bodyText.replacingOccurrences(of: \"\\r\", with: \"\\n\")\n        let lines = cleaned\n            .split(whereSeparator: \\.isNewline)\n            .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }\n            .filter { !$0.isEmpty }\n\n        let primary = self.parseRateWindow(\n            lines: lines,\n            match: self.isFiveHourLimitLine,\n            windowMinutes: 5 * 60,\n            now: now)\n        let secondary = self.parseRateWindow(\n            lines: lines,\n            match: self.isWeeklyLimitLine,\n            windowMinutes: 7 * 24 * 60,\n            now: now)\n        return (primary, secondary)\n    }\n\n    public static func parsePlanFromHTML(html: String) -> String? {\n        if let data = self.clientBootstrapJSONData(fromHTML: html),\n           let plan = self.findPlan(in: data)\n        {\n            return plan\n        }\n        if let data = self.nextDataJSONData(fromHTML: html),\n           let plan = self.findPlan(in: data)\n        {\n            return plan\n        }\n        return nil\n    }\n\n    public static func parseCreditEvents(rows: [[String]]) -> [CreditEvent] {\n        let formatter = self.creditDateFormatter()\n\n        return rows.compactMap { row in\n            guard row.count >= 3 else { return nil }\n            let dateString = row[0]\n            let service = row[1].trimmingCharacters(in: .whitespacesAndNewlines)\n            let amountString = row[2]\n            guard let date = formatter.date(from: dateString) else { return nil }\n            let creditsUsed = Self.parseCreditsUsed(amountString)\n            return CreditEvent(date: date, service: service, creditsUsed: creditsUsed)\n        }\n        .sorted { $0.date > $1.date }\n    }\n\n    private static func parseCreditsUsed(_ text: String) -> Double {\n        let cleaned = text\n            .replacingOccurrences(of: \",\", with: \"\")\n            .replacingOccurrences(of: \"credits\", with: \"\", options: .caseInsensitive)\n            .trimmingCharacters(in: .whitespacesAndNewlines)\n        return Double(cleaned) ?? 0\n    }\n\n    // MARK: - Private\n\n    private static let codeReviewRegexes: [NSRegularExpression] = {\n        let patterns = [\n            #\"Code\\s*review[^0-9%]*([0-9]{1,3})%\\s*remaining\"#,\n            #\"Core\\s*review[^0-9%]*([0-9]{1,3})%\\s*remaining\"#,\n        ]\n        return patterns.compactMap { pattern in\n            try? NSRegularExpression(pattern: pattern, options: [.caseInsensitive])\n        }\n    }()\n\n    private static let creditDateFormatterKey = \"OpenAIDashboardParser.creditDateFormatter\"\n    private static let clientBootstrapNeedle = Data(\"id=\\\"client-bootstrap\\\"\".utf8)\n    private static let nextDataNeedle = Data(\"id=\\\"__NEXT_DATA__\\\"\".utf8)\n    private static let scriptCloseNeedle = Data(\"</script>\".utf8)\n\n    private static func creditDateFormatter() -> DateFormatter {\n        let threadDict = Thread.current.threadDictionary\n        if let cached = threadDict[self.creditDateFormatterKey] as? DateFormatter {\n            return cached\n        }\n        let formatter = DateFormatter()\n        formatter.locale = Locale(identifier: \"en_US_POSIX\")\n        formatter.dateFormat = \"MMM d, yyyy\"\n        threadDict[self.creditDateFormatterKey] = formatter\n        return formatter\n    }\n\n    private static func clientBootstrapJSONData(fromHTML html: String) -> Data? {\n        let data = Data(html.utf8)\n        guard let idRange = data.range(of: self.clientBootstrapNeedle) else { return nil }\n\n        guard let openTagEnd = data[idRange.upperBound...].firstIndex(of: UInt8(ascii: \">\")) else { return nil }\n        let contentStart = data.index(after: openTagEnd)\n        guard let closeRange = data.range(\n            of: self.scriptCloseNeedle,\n            options: [],\n            in: contentStart..<data.endIndex)\n        else {\n            return nil\n        }\n        let rawData = data[contentStart..<closeRange.lowerBound]\n        let trimmed = self.trimASCIIWhitespace(Data(rawData))\n        return trimmed.isEmpty ? nil : trimmed\n    }\n\n    private static func nextDataJSONData(fromHTML html: String) -> Data? {\n        let data = Data(html.utf8)\n        guard let idRange = data.range(of: self.nextDataNeedle) else { return nil }\n\n        guard let openTagEnd = data[idRange.upperBound...].firstIndex(of: UInt8(ascii: \">\")) else { return nil }\n        let contentStart = data.index(after: openTagEnd)\n        guard let closeRange = data.range(\n            of: self.scriptCloseNeedle,\n            options: [],\n            in: contentStart..<data.endIndex)\n        else {\n            return nil\n        }\n        let rawData = data[contentStart..<closeRange.lowerBound]\n        let trimmed = self.trimASCIIWhitespace(Data(rawData))\n        return trimmed.isEmpty ? nil : trimmed\n    }\n\n    private static func trimASCIIWhitespace(_ data: Data) -> Data {\n        guard !data.isEmpty else { return data }\n        var start = data.startIndex\n        var end = data.endIndex\n\n        while start < end, data[start].isASCIIWhitespace {\n            start = data.index(after: start)\n        }\n        while end > start {\n            let prev = data.index(before: end)\n            if data[prev].isASCIIWhitespace {\n                end = prev\n            } else {\n                break\n            }\n        }\n        return data.subdata(in: start..<end)\n    }\n\n    private static func parseRateWindow(\n        lines: [String],\n        match: (String) -> Bool,\n        windowMinutes: Int,\n        now: Date) -> RateWindow?\n    {\n        guard let idx = lines.firstIndex(where: match) else { return nil }\n        let end = min(lines.count - 1, idx + 5)\n        let windowLines = Array(lines[idx...end])\n\n        var percentValue: Double?\n        var isRemaining = true\n        for line in windowLines {\n            if let percent = self.parsePercent(from: line) {\n                percentValue = percent.value\n                isRemaining = percent.isRemaining\n                break\n            }\n        }\n\n        guard let percentValue else { return nil }\n        let usedPercent = isRemaining ? max(0, min(100, 100 - percentValue)) : max(0, min(100, percentValue))\n\n        let resetLine = windowLines.first { $0.localizedCaseInsensitiveContains(\"reset\") }\n        let resetDescription = resetLine?.trimmingCharacters(in: .whitespacesAndNewlines)\n        let resetsAt = resetLine.flatMap { self.parseResetDate(from: $0, now: now) }\n        let fallbackDescription = resetsAt.map { UsageFormatter.resetDescription(from: $0) }\n\n        return RateWindow(\n            usedPercent: usedPercent,\n            windowMinutes: windowMinutes,\n            resetsAt: resetsAt,\n            resetDescription: resetDescription ?? fallbackDescription)\n    }\n\n    private static func parsePercent(from line: String) -> (value: Double, isRemaining: Bool)? {\n        guard let percent = TextParsing.firstNumber(pattern: #\"([0-9]{1,3})\\s*%\"#, text: line) else { return nil }\n        let lower = line.lowercased()\n        let isRemaining = lower.contains(\"remaining\") || lower.contains(\"left\")\n        let isUsed = lower.contains(\"used\") || lower.contains(\"spent\") || lower.contains(\"consumed\")\n        if isUsed { return (percent, false) }\n        if isRemaining { return (percent, true) }\n        return (percent, true)\n    }\n\n    private static func isFiveHourLimitLine(_ line: String) -> Bool {\n        let lower = line.lowercased()\n        if lower.contains(\"5h\") { return true }\n        if lower.contains(\"5-hour\") { return true }\n        if lower.contains(\"5 hour\") { return true }\n        return false\n    }\n\n    private static func isWeeklyLimitLine(_ line: String) -> Bool {\n        let lower = line.lowercased()\n        if lower.contains(\"weekly\") { return true }\n        if lower.contains(\"7-day\") { return true }\n        if lower.contains(\"7 day\") { return true }\n        if lower.contains(\"7d\") { return true }\n        return false\n    }\n\n    private static func parseResetDate(from line: String, now: Date) -> Date? {\n        var raw = line.trimmingCharacters(in: .whitespacesAndNewlines)\n        raw = raw.replacingOccurrences(of: #\"(?i)^resets?:?\\s*\"#, with: \"\", options: .regularExpression)\n        raw = raw.replacingOccurrences(of: \" at \", with: \" \", options: .caseInsensitive)\n        raw = raw.replacingOccurrences(of: \" on \", with: \" \", options: .caseInsensitive)\n        raw = raw.replacingOccurrences(of: #\"\\s+\"#, with: \" \", options: .regularExpression)\n            .trimmingCharacters(in: .whitespacesAndNewlines)\n\n        let calendar = Calendar(identifier: .gregorian)\n        let monthDayFormatter = DateFormatter()\n        monthDayFormatter.locale = Locale(identifier: \"en_US_POSIX\")\n        monthDayFormatter.timeZone = TimeZone.current\n        monthDayFormatter.dateFormat = \"MMM d\"\n\n        var candidate = raw\n        let lower = candidate.lowercased()\n        var usedRelativeDay = false\n\n        if lower.contains(\"today\") {\n            usedRelativeDay = true\n            let dateText = monthDayFormatter.string(from: now)\n            candidate = candidate.replacingOccurrences(of: \"today\", with: dateText, options: .caseInsensitive)\n        } else if lower.contains(\"tomorrow\") {\n            usedRelativeDay = true\n            if let tomorrow = calendar.date(byAdding: .day, value: 1, to: now) {\n                let dateText = monthDayFormatter.string(from: tomorrow)\n                candidate = candidate.replacingOccurrences(of: \"tomorrow\", with: dateText, options: .caseInsensitive)\n            }\n        }\n\n        if let weekdayMatch = self.weekdayMatch(in: candidate) {\n            usedRelativeDay = true\n            let target = self.nextWeekdayDate(weekday: weekdayMatch.weekday, now: now, calendar: calendar)\n            let dateText = monthDayFormatter.string(from: target)\n            candidate = candidate.replacingOccurrences(\n                of: weekdayMatch.matched,\n                with: dateText,\n                options: .caseInsensitive)\n        }\n\n        let formatter = DateFormatter()\n        formatter.locale = Locale(identifier: \"en_US_POSIX\")\n        formatter.timeZone = TimeZone.current\n        formatter.defaultDate = now\n\n        let formats = [\n            \"MMM d h:mma\",\n            \"MMM d, h:mma\",\n            \"MMM d h:mm a\",\n            \"MMM d, h:mm a\",\n            \"MMM d HH:mm\",\n            \"MMM d, HH:mm\",\n            \"MMM d\",\n            \"M/d h:mma\",\n            \"M/d h:mm a\",\n            \"M/d/yyyy h:mm a\",\n            \"M/d/yy h:mm a\",\n            \"M/d\",\n            \"yyyy-MM-dd HH:mm\",\n            \"yyyy-MM-dd h:mm a\",\n            \"yyyy-MM-dd\",\n        ]\n\n        for format in formats {\n            formatter.dateFormat = format\n            if let date = formatter.date(from: candidate) {\n                if usedRelativeDay, date < now {\n                    if lower.contains(\"today\"),\n                       let bumped = calendar.date(byAdding: .day, value: 1, to: date)\n                    {\n                        return bumped\n                    }\n                    if let bumped = calendar.date(byAdding: .day, value: 7, to: date) {\n                        return bumped\n                    }\n                }\n                return date\n            }\n        }\n        return nil\n    }\n\n    private struct WeekdayMatch {\n        let matched: String\n        let weekday: Int\n    }\n\n    private static func weekdayMatch(in text: String) -> WeekdayMatch? {\n        let pattern = #\"\\b(mon|tue|tues|wed|thu|thur|thurs|fri|sat|sun)(day)?\\b\"#\n        guard let regex = try? NSRegularExpression(pattern: pattern, options: [.caseInsensitive]) else { return nil }\n        let range = NSRange(text.startIndex..<text.endIndex, in: text)\n        guard let match = regex.firstMatch(in: text, options: [], range: range),\n              let r = Range(match.range(at: 0), in: text)\n        else { return nil }\n        let matched = String(text[r])\n        let lower = matched.lowercased()\n        let weekday = switch lower.prefix(3) {\n        case \"mon\": 2\n        case \"tue\": 3\n        case \"wed\": 4\n        case \"thu\": 5\n        case \"fri\": 6\n        case \"sat\": 7\n        default: 1\n        }\n        return WeekdayMatch(matched: matched, weekday: weekday)\n    }\n\n    private static func nextWeekdayDate(weekday: Int, now: Date, calendar: Calendar) -> Date {\n        let currentWeekday = calendar.component(.weekday, from: now)\n        var delta = weekday - currentWeekday\n        if delta < 0 { delta += 7 }\n        guard let next = calendar.date(byAdding: .day, value: delta, to: calendar.startOfDay(for: now)) else {\n            return now\n        }\n        return next\n    }\n\n    private static func findPlan(in data: Data) -> String? {\n        guard let json = try? JSONSerialization.jsonObject(with: data, options: []) else { return nil }\n        return self.findPlan(in: json)\n    }\n\n    private static func findPlan(in json: Any) -> String? {\n        var queue: [Any] = [json]\n        var seen = 0\n        while !queue.isEmpty, seen < 6000 {\n            let cur = queue.removeFirst()\n            seen += 1\n            if let dict = cur as? [String: Any] {\n                for (k, v) in dict {\n                    if let plan = self.planCandidate(forKey: k, value: v) { return plan }\n                    queue.append(v)\n                }\n            } else if let arr = cur as? [Any] {\n                queue.append(contentsOf: arr)\n            }\n        }\n        return nil\n    }\n\n    private static func planCandidate(forKey key: String, value: Any) -> String? {\n        guard self.isPlanKey(key) else { return nil }\n        if let str = value as? String {\n            return self.normalizePlanValue(str)\n        }\n        if let dict = value as? [String: Any] {\n            if let name = dict[\"name\"] as? String, let plan = self.normalizePlanValue(name) { return plan }\n            if let display = dict[\"displayName\"] as? String, let plan = self.normalizePlanValue(display) { return plan }\n            if let tier = dict[\"tier\"] as? String, let plan = self.normalizePlanValue(tier) { return plan }\n        }\n        return nil\n    }\n\n    private static func isPlanKey(_ key: String) -> Bool {\n        let lower = key.lowercased()\n        return lower.contains(\"plan\") || lower.contains(\"tier\") || lower.contains(\"subscription\")\n    }\n\n    private static func normalizePlanValue(_ value: String) -> String? {\n        let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)\n        guard !trimmed.isEmpty else { return nil }\n        let lower = trimmed.lowercased()\n        let allowed = [\n            \"free\",\n            \"plus\",\n            \"pro\",\n            \"team\",\n            \"enterprise\",\n            \"business\",\n            \"edu\",\n            \"education\",\n            \"gov\",\n            \"premium\",\n            \"essential\",\n        ]\n        guard allowed.contains(where: { lower.contains($0) }) else { return nil }\n        return UsageFormatter.cleanPlanName(trimmed)\n    }\n}\n\nextension UInt8 {\n    fileprivate var isASCIIWhitespace: Bool {\n        switch self {\n        case 9, 10, 13, 32: true\n        default: false\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/OpenAIWeb/OpenAIDashboardScrapeScript.swift",
    "content": "#if os(macOS)\nlet openAIDashboardScrapeScript = \"\"\"\n\n    (() => {\n      const textOf = el => {\n        const raw = el && (el.innerText || el.textContent) ? String(el.innerText || el.textContent) : '';\n        return raw.trim();\n      };\n      const parseHexColor = (color) => {\n        if (!color) return null;\n        const c = String(color).trim().toLowerCase();\n        if (c.startsWith('#')) {\n          if (c.length === 4) {\n            return '#' + c[1] + c[1] + c[2] + c[2] + c[3] + c[3];\n          }\n          if (c.length === 7) return c;\n          return c;\n        }\n        const m = c.match(/^rgba?\\\\(([^)]+)\\\\)$/);\n        if (m) {\n          const parts = m[1].split(',').map(x => parseFloat(x.trim())).filter(x => Number.isFinite(x));\n          if (parts.length >= 3) {\n            const r = Math.max(0, Math.min(255, Math.round(parts[0])));\n            const g = Math.max(0, Math.min(255, Math.round(parts[1])));\n            const b = Math.max(0, Math.min(255, Math.round(parts[2])));\n            const toHex = n => n.toString(16).padStart(2, '0');\n            return '#' + toHex(r) + toHex(g) + toHex(b);\n          }\n        }\n        return c;\n      };\n      const reactPropsOf = (el) => {\n        if (!el) return null;\n        try {\n          const keys = Object.keys(el);\n          const propsKey = keys.find(k => k.startsWith('__reactProps$'));\n          if (propsKey) return el[propsKey] || null;\n          const fiberKey = keys.find(k => k.startsWith('__reactFiber$'));\n          if (fiberKey) {\n            const fiber = el[fiberKey];\n            return (fiber && (fiber.memoizedProps || fiber.pendingProps)) || null;\n          }\n        } catch {}\n        return null;\n      };\n      const reactFiberOf = (el) => {\n        if (!el) return null;\n        try {\n          const keys = Object.keys(el);\n          const fiberKey = keys.find(k => k.startsWith('__reactFiber$'));\n          return fiberKey ? (el[fiberKey] || null) : null;\n        } catch {\n          return null;\n        }\n      };\n      const nestedBarMetaOf = (root) => {\n        if (!root || typeof root !== 'object') return null;\n        const queue = [root];\n        const seen = typeof WeakSet !== 'undefined' ? new WeakSet() : null;\n        let steps = 0;\n        while (queue.length && steps < 250) {\n          const cur = queue.shift();\n          steps++;\n          if (!cur || typeof cur !== 'object') continue;\n          if (seen) {\n            if (seen.has(cur)) continue;\n            seen.add(cur);\n          }\n          if (cur.payload && (cur.dataKey || cur.name || cur.value !== undefined)) return cur;\n          const values = Array.isArray(cur) ? cur : Object.values(cur);\n          for (const v of values) {\n            if (v && typeof v === 'object') queue.push(v);\n          }\n        }\n        return null;\n      };\n      const barMetaFromElement = (el) => {\n        const direct = reactPropsOf(el);\n        if (direct && direct.payload && (direct.dataKey || direct.name || direct.value !== undefined)) return direct;\n\n        const fiber = reactFiberOf(el);\n        if (fiber) {\n          let cur = fiber;\n          for (let i = 0; i < 10 && cur; i++) {\n            const props = (cur.memoizedProps || cur.pendingProps) || null;\n            if (props && props.payload && (props.dataKey || props.name || props.value !== undefined)) return props;\n            const nested = props ? nestedBarMetaOf(props) : null;\n            if (nested) return nested;\n            cur = cur.return || null;\n          }\n        }\n\n        if (direct) {\n          const nested = nestedBarMetaOf(direct);\n          if (nested) return nested;\n        }\n        return null;\n      };\n      const normalizeHref = (raw) => {\n        if (!raw) return null;\n        const href = String(raw).trim();\n        if (!href) return null;\n        if (href.startsWith('http://') || href.startsWith('https://')) return href;\n        if (href.startsWith('//')) return window.location.protocol + href;\n        if (href.startsWith('/')) return window.location.origin + href;\n        return window.location.origin + '/' + href;\n      };\n      const isLikelyCreditsURL = (raw) => {\n        if (!raw) return false;\n        try {\n          const url = new URL(raw, window.location.origin);\n          if (!url.host || !url.host.includes('chatgpt.com')) return false;\n          const path = url.pathname.toLowerCase();\n          return (\n            path.includes('settings') ||\n            path.includes('usage') ||\n            path.includes('billing') ||\n            path.includes('credits')\n          );\n        } catch {\n          return false;\n        }\n      };\n      const purchaseTextMatches = (text) => {\n        const lower = String(text || '').trim().toLowerCase();\n        if (!lower) return false;\n        if (lower.includes('add more')) return true;\n        if (!lower.includes('credit')) return false;\n        return (\n          lower.includes('buy') ||\n          lower.includes('add') ||\n          lower.includes('purchase') ||\n          lower.includes('top up') ||\n          lower.includes('top-up')\n        );\n      };\n      const elementLabel = (el) => {\n        if (!el) return '';\n        return (\n          textOf(el) ||\n          el.getAttribute('aria-label') ||\n          el.getAttribute('title') ||\n          ''\n        );\n      };\n      const urlFromProps = (props) => {\n        if (!props || typeof props !== 'object') return null;\n        const candidates = [\n          props.href,\n          props.to,\n          props.url,\n          props.link,\n          props.destination,\n          props.navigateTo\n        ];\n        for (const candidate of candidates) {\n          if (typeof candidate === 'string' && candidate.trim()) {\n            return normalizeHref(candidate);\n          }\n        }\n        return null;\n      };\n      const purchaseURLFromElement = (el) => {\n        if (!el) return null;\n        const isAnchor = el.tagName && el.tagName.toLowerCase() === 'a';\n        const anchor = isAnchor ? el : (el.closest ? el.closest('a') : null);\n        const anchorHref = anchor ? anchor.getAttribute('href') : null;\n        const dataHref = el.getAttribute\n          ? (el.getAttribute('data-href') ||\n            el.getAttribute('data-url') ||\n            el.getAttribute('data-link') ||\n            el.getAttribute('data-destination'))\n          : null;\n        const propHref = urlFromProps(reactPropsOf(el)) || urlFromProps(reactPropsOf(anchor));\n        const normalized = normalizeHref(anchorHref || dataHref || propHref);\n        return normalized && isLikelyCreditsURL(normalized) ? normalized : null;\n      };\n      const pickLikelyPurchaseButton = (buttons) => {\n        if (!buttons || buttons.length === 0) return null;\n        const labeled = buttons.find(btn => {\n          const label = elementLabel(btn);\n          if (purchaseTextMatches(label)) return true;\n          const aria = String(btn.getAttribute('aria-label') || '').toLowerCase();\n          return aria.includes('credit') || aria.includes('buy') || aria.includes('add');\n        });\n        return labeled || buttons[0];\n      };\n      const findCreditsPurchaseButton = () => {\n        const nodes = Array.from(document.querySelectorAll('h1,h2,h3,div,span,p'));\n        const labelMatch = nodes.find(node => {\n          const lower = textOf(node).toLowerCase();\n          return lower === 'credits remaining' || (lower.includes('credits') && lower.includes('remaining'));\n        });\n        if (!labelMatch) return null;\n        let cur = labelMatch;\n        for (let i = 0; i < 6 && cur; i++) {\n          const buttons = Array.from(cur.querySelectorAll('button, a'));\n          const picked = pickLikelyPurchaseButton(buttons);\n          if (picked) return picked;\n          cur = cur.parentElement;\n        }\n        return null;\n      };\n      const dayKeyFromPayload = (payload) => {\n        if (!payload || typeof payload !== 'object') return null;\n        const localDayKeyForDate = (date) => {\n          const year = date.getFullYear();\n          const month = String(date.getMonth() + 1).padStart(2, '0');\n          const day = String(date.getDate()).padStart(2, '0');\n          return `${year}-${month}-${day}`;\n        };\n        const keys = ['day', 'date', 'name', 'label', 'x', 'time', 'timestamp'];\n        for (const k of keys) {\n          const v = payload[k];\n          if (typeof v === 'string') {\n            const s = v.trim();\n            if (/^\\\\d{4}-\\\\d{2}-\\\\d{2}$/.test(s)) return s;\n            const iso = s.match(/^(\\\\d{4}-\\\\d{2}-\\\\d{2})/);\n            if (iso) return iso[1];\n          }\n          if (typeof v === 'number' && Number.isFinite(v) && (k === 'timestamp' || k === 'time' || k === 'x')) {\n            try {\n              const d = new Date(v);\n              if (!isNaN(d.getTime())) return localDayKeyForDate(d);\n            } catch {}\n          }\n        }\n        return null;\n      };\n      const displayNameForUsageServiceKey = (raw) => {\n        const key = raw === null || raw === undefined ? '' : String(raw).trim();\n        if (!key) return key;\n        if (key.toUpperCase() === key && key.length <= 6) return key;\n        const lower = key.toLowerCase();\n        if (lower === 'cli') return 'CLI';\n        if (lower.includes('github') && lower.includes('review')) return 'GitHub Code Review';\n        const words = lower.replace(/[_-]+/g, ' ').split(' ').filter(Boolean);\n        return words.map(w => w.length <= 2 ? w.toUpperCase() : w.charAt(0).toUpperCase() + w.slice(1)).join(' ');\n      };\n      const usageBreakdownJSON = (() => {\n        try {\n          if (window.__codexbarUsageBreakdownJSON) return window.__codexbarUsageBreakdownJSON;\n\n          const sections = Array.from(document.querySelectorAll('section'));\n          const usageSection = sections.find(s => {\n            const h2 = s.querySelector('h2');\n            return h2 && textOf(h2).toLowerCase().startsWith('usage breakdown');\n          });\n          if (!usageSection) return null;\n\n          const legendMap = {};\n          try {\n            const legendItems = Array.from(usageSection.querySelectorAll('div[title]'));\n            for (const item of legendItems) {\n              const title = item.getAttribute('title') ? String(item.getAttribute('title')).trim() : '';\n              const square = item.querySelector('div[style*=\\\"background-color\\\"]');\n              const color = (square && square.style && square.style.backgroundColor)\n                ? square.style.backgroundColor\n                : null;\n              const hex = parseHexColor(color);\n              if (title && hex) legendMap[hex] = title;\n            }\n          } catch {}\n\n          const totalsByDay = {}; // day -> service -> value\n          const paths = Array.from(usageSection.querySelectorAll('g.recharts-bar-rectangle path.recharts-rectangle'));\n          let debug = {\n            pathCount: paths.length,\n            sampleReactKeys: null,\n            sampleMetaKeys: null,\n            samplePayloadKeys: null,\n            sampleValuesKeys: null,\n            sampleDayKey: null\n          };\n          try {\n            const sample = paths[0] || null;\n            if (sample) {\n              const names = Object.getOwnPropertyNames(sample);\n              debug.sampleReactKeys = names.filter(k => k.includes('react')).slice(0, 10);\n              const metaSample = barMetaFromElement(sample) || barMetaFromElement(sample.parentElement) || null;\n              if (metaSample) {\n                debug.sampleMetaKeys = Object.keys(metaSample).slice(0, 12);\n                const payload = metaSample.payload || null;\n                if (payload && typeof payload === 'object') {\n                  debug.samplePayloadKeys = Object.keys(payload).slice(0, 12);\n                  debug.sampleDayKey = dayKeyFromPayload(payload);\n                  const values = payload.values || null;\n                  if (values && typeof values === 'object') {\n                    debug.sampleValuesKeys = Object.keys(values).slice(0, 12);\n                  }\n                }\n              }\n            }\n          } catch {}\n          for (const path of paths) {\n            const meta = barMetaFromElement(path) || barMetaFromElement(path.parentElement) || null;\n            if (!meta) continue;\n\n            const payload = meta.payload || null;\n            const day = dayKeyFromPayload(payload);\n            if (!day) continue;\n\n            const valuesObj = (payload && payload.values && typeof payload.values === 'object') ? payload.values : null;\n            if (valuesObj) {\n              if (!totalsByDay[day]) totalsByDay[day] = {};\n              for (const [k, v] of Object.entries(valuesObj)) {\n                if (typeof v !== 'number' || !Number.isFinite(v) || v <= 0) continue;\n                const service = displayNameForUsageServiceKey(k);\n                if (!service) continue;\n                totalsByDay[day][service] = (totalsByDay[day][service] || 0) + v;\n              }\n              continue;\n            }\n\n            let value = null;\n            if (typeof meta.value === 'number' && Number.isFinite(meta.value)) value = meta.value;\n            if (value === null && typeof meta.value === 'string') {\n              const v = parseFloat(meta.value.replace(/,/g, ''));\n              if (Number.isFinite(v)) value = v;\n            }\n            if (value === null) continue;\n\n            const fill = parseHexColor(meta.fill || path.getAttribute('fill'));\n            const service =\n              (fill && legendMap[fill]) ||\n              (typeof meta.name === 'string' && meta.name) ||\n              null;\n            if (!service) continue;\n\n            if (!totalsByDay[day]) totalsByDay[day] = {};\n            totalsByDay[day][service] = (totalsByDay[day][service] || 0) + value;\n          }\n\n          const dayKeys = Object.keys(totalsByDay).sort((a, b) => b.localeCompare(a)).slice(0, 30);\n          const breakdown = dayKeys.map(day => {\n            const servicesMap = totalsByDay[day] || {};\n            const services = Object.keys(servicesMap).map(service => ({\n              service,\n              creditsUsed: servicesMap[service]\n            })).sort((a, b) => {\n              if (a.creditsUsed === b.creditsUsed) return a.service.localeCompare(b.service);\n              return b.creditsUsed - a.creditsUsed;\n            });\n            const totalCreditsUsed = services.reduce((sum, s) => sum + (Number(s.creditsUsed) || 0), 0);\n            return { day, services, totalCreditsUsed };\n          });\n\n          const json = (breakdown.length > 0) ? JSON.stringify(breakdown) : null;\n          window.__codexbarUsageBreakdownJSON = json;\n          window.__codexbarUsageBreakdownDebug = json ? null : JSON.stringify(debug);\n          return json;\n        } catch {\n          return null;\n        }\n      })();\n      const usageBreakdownDebug = (() => {\n        try {\n          return window.__codexbarUsageBreakdownDebug || null;\n        } catch {\n          return null;\n        }\n      })();\n      const bodyText = document.body ? String(document.body.innerText || '').trim() : '';\n      const href = window.location ? String(window.location.href || '') : '';\n      const workspacePicker = bodyText.includes('Select a workspace');\n      const title = document.title ? String(document.title || '') : '';\n      const cloudflareInterstitial =\n        title.toLowerCase().includes('just a moment') ||\n        bodyText.toLowerCase().includes('checking your browser') ||\n        bodyText.toLowerCase().includes('cloudflare');\n      const authSelector = [\n        'input[type=\"email\"]',\n        'input[type=\"password\"]',\n        'input[name=\"username\"]'\n      ].join(', ');\n      const hasAuthInputs = !!document.querySelector(authSelector);\n      const lower = bodyText.toLowerCase();\n      const loginCTA =\n        lower.includes('sign in') ||\n        lower.includes('log in') ||\n        lower.includes('continue with google') ||\n        lower.includes('continue with apple') ||\n        lower.includes('continue with microsoft');\n      const loginRequired =\n        href.includes('/auth/') ||\n        href.includes('/login') ||\n        (hasAuthInputs && loginCTA) ||\n        (!hasAuthInputs && loginCTA && href.includes('chatgpt.com'));\n      const scrollY = (typeof window.scrollY === 'number') ? window.scrollY : 0;\n      const scrollHeight = document.documentElement ? (document.documentElement.scrollHeight || 0) : 0;\n      const viewportHeight = (typeof window.innerHeight === 'number') ? window.innerHeight : 0;\n\n      let creditsHeaderPresent = false;\n      let creditsHeaderInViewport = false;\n      let didScrollToCredits = false;\n      let rows = [];\n      try {\n        const headings = Array.from(document.querySelectorAll('h1,h2,h3'));\n        const header = headings.find(h => textOf(h).toLowerCase() === 'credits usage history');\n        if (header) {\n          creditsHeaderPresent = true;\n          const rect = header.getBoundingClientRect();\n          creditsHeaderInViewport = rect.top >= 0 && rect.top <= viewportHeight;\n\n          // Only scrape rows from the *credits usage history* table. The page can contain other tables,\n          // and treating any <table> as credits history can prevent our scroll-to-load logic from running.\n          const container = header.closest('section') || header.parentElement || document;\n          const table = container.querySelector('table') || null;\n          const scope = table || container;\n          rows = Array.from(scope.querySelectorAll('tbody tr')).map(tr => {\n            const cells = Array.from(tr.querySelectorAll('td')).map(td => textOf(td));\n            return cells;\n          }).filter(r => r.length >= 3);\n          if (rows.length === 0 && !window.__codexbarDidScrollToCredits) {\n            window.__codexbarDidScrollToCredits = true;\n            // If the table is virtualized/lazy-loaded, we need to scroll to trigger rendering even if the\n            // header is already in view.\n            header.scrollIntoView({ block: 'start', inline: 'nearest' });\n            if (creditsHeaderInViewport) {\n              window.scrollBy(0, Math.max(220, viewportHeight * 0.6));\n            }\n            didScrollToCredits = true;\n          }\n        } else if (rows.length === 0 && !window.__codexbarDidScrollToCredits && scrollHeight > viewportHeight * 1.5) {\n          // The credits history section often isn't part of the DOM until you scroll down. Nudge the page\n          // once so subsequent scrapes can find the header and rows.\n          window.__codexbarDidScrollToCredits = true;\n          window.scrollTo(0, Math.max(0, scrollHeight - viewportHeight - 40));\n          didScrollToCredits = true;\n        }\n      } catch {}\n\n      let creditsPurchaseURL = null;\n      try {\n        const creditsButton = findCreditsPurchaseButton();\n        if (creditsButton) {\n          const url = purchaseURLFromElement(creditsButton);\n          if (url) creditsPurchaseURL = url;\n        }\n        const candidates = Array.from(document.querySelectorAll('a, button'));\n        for (const node of candidates) {\n          const label = elementLabel(node);\n          if (!purchaseTextMatches(label)) continue;\n          const url = purchaseURLFromElement(node);\n          if (url) {\n            creditsPurchaseURL = url;\n            break;\n          }\n        }\n        if (!creditsPurchaseURL) {\n          const anchors = Array.from(document.querySelectorAll('a[href]'));\n          for (const anchor of anchors) {\n            const label = elementLabel(anchor);\n            const href = anchor.getAttribute('href') || '';\n            const hrefLooksRelevant = /credits|billing/i.test(href);\n            if (!hrefLooksRelevant && !purchaseTextMatches(label)) continue;\n            const url = normalizeHref(href);\n            if (url) {\n              creditsPurchaseURL = url;\n              break;\n            }\n          }\n        }\n      } catch {}\n\n      let signedInEmail = null;\n      try {\n        const next = window.__NEXT_DATA__ || null;\n        const props = (next && next.props && next.props.pageProps) ? next.props.pageProps : null;\n        const userEmail = (props && props.user) ? props.user.email : null;\n        const sessionEmail = (props && props.session && props.session.user) ? props.session.user.email : null;\n        signedInEmail = userEmail || sessionEmail || null;\n      } catch {}\n\n      if (!signedInEmail) {\n        try {\n          const node = document.getElementById('__NEXT_DATA__');\n          const raw = node && node.textContent ? String(node.textContent) : '';\n          if (raw) {\n            const obj = JSON.parse(raw);\n            const queue = [obj];\n            let seen = 0;\n            while (queue.length && seen < 2000 && !signedInEmail) {\n              const cur = queue.shift();\n              seen++;\n              if (!cur) continue;\n              if (typeof cur === 'string') {\n                if (cur.includes('@')) signedInEmail = cur;\n                continue;\n              }\n              if (typeof cur !== 'object') continue;\n              for (const [k, v] of Object.entries(cur)) {\n                if (signedInEmail) break;\n                if (k === 'email' && typeof v === 'string' && v.includes('@')) {\n                  signedInEmail = v;\n                  break;\n                }\n                if (typeof v === 'object' && v) queue.push(v);\n              }\n            }\n          }\n        } catch {}\n      }\n\n      if (!signedInEmail) {\n        try {\n          const emailRe = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\\\\.[A-Z]{2,}/ig;\n          const found = (bodyText.match(emailRe) || []).map(x => String(x).trim().toLowerCase());\n          const unique = Array.from(new Set(found));\n          if (unique.length === 1) {\n            signedInEmail = unique[0];\n          } else if (unique.length > 1) {\n            signedInEmail = unique[0];\n          }\n        } catch {}\n      }\n\n      if (!signedInEmail) {\n        // Last resort: open the account menu so the email becomes part of the DOM text.\n        try {\n          const emailRe = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\\\\.[A-Z]{2,}/ig;\n          const hasMenu = Boolean(document.querySelector('[role=\"menu\"]'));\n          if (!hasMenu) {\n            const button =\n              document.querySelector('button[aria-haspopup=\"menu\"]') ||\n              document.querySelector('button[aria-expanded]');\n            if (button && !button.disabled) {\n              button.click();\n            }\n          }\n          const afterText = document.body ? String(document.body.innerText || '').trim() : '';\n          const found = (afterText.match(emailRe) || []).map(x => String(x).trim().toLowerCase());\n          const unique = Array.from(new Set(found));\n          if (unique.length === 1) {\n            signedInEmail = unique[0];\n          } else if (unique.length > 1) {\n            signedInEmail = unique[0];\n          }\n        } catch {}\n      }\n\n      return {\n        loginRequired,\n        workspacePicker,\n        cloudflareInterstitial,\n        href,\n        bodyText,\n        bodyHTML: document.documentElement ? String(document.documentElement.outerHTML || '') : '',\n        signedInEmail,\n        creditsPurchaseURL,\n        rows,\n        usageBreakdownJSON,\n        usageBreakdownDebug,\n        scrollY,\n        scrollHeight,\n        viewportHeight,\n        creditsHeaderPresent,\n        creditsHeaderInViewport,\n        didScrollToCredits\n      };\n    })();\n\n\"\"\"\n#endif\n"
  },
  {
    "path": "Sources/CodexBarCore/OpenAIWeb/OpenAIDashboardWebViewCache.swift",
    "content": "#if os(macOS)\nimport AppKit\nimport Foundation\nimport WebKit\n\nstruct OpenAIDashboardWebViewLease {\n    let webView: WKWebView\n    let log: (String) -> Void\n    let release: () -> Void\n}\n\n@MainActor\nfinal class OpenAIDashboardWebViewCache {\n    static let shared = OpenAIDashboardWebViewCache()\n    fileprivate static let log = CodexBarLog.logger(LogCategories.openAIWebview)\n\n    private final class Entry {\n        let webView: WKWebView\n        let host: OffscreenWebViewHost\n        var lastUsedAt: Date\n        var isBusy: Bool\n\n        init(webView: WKWebView, host: OffscreenWebViewHost, lastUsedAt: Date, isBusy: Bool) {\n            self.webView = webView\n            self.host = host\n            self.lastUsedAt = lastUsedAt\n            self.isBusy = isBusy\n        }\n    }\n\n    private var entries: [ObjectIdentifier: Entry] = [:]\n    private let idleTimeout: TimeInterval = 10 * 60\n\n    // MARK: - Testing support\n\n    #if DEBUG\n    /// Number of cached WebView entries (for testing).\n    var entryCount: Int {\n        self.entries.count\n    }\n\n    /// Check if a WebView is cached for the given data store (for testing).\n    func hasCachedEntry(for websiteDataStore: WKWebsiteDataStore) -> Bool {\n        let key = ObjectIdentifier(websiteDataStore)\n        return self.entries[key] != nil\n    }\n\n    /// Force prune with a custom \"now\" timestamp (for testing idle timeout).\n    func pruneForTesting(now: Date) {\n        self.prune(now: now)\n    }\n\n    /// Clear all cached entries (for test isolation).\n    func clearAllForTesting() {\n        for (_, entry) in self.entries {\n            entry.host.close()\n        }\n        self.entries.removeAll()\n    }\n    #endif\n\n    func acquire(\n        websiteDataStore: WKWebsiteDataStore,\n        usageURL: URL,\n        logger: ((String) -> Void)?,\n        navigationTimeout: TimeInterval = 15) async throws -> OpenAIDashboardWebViewLease\n    {\n        let now = Date()\n        self.prune(now: now)\n\n        let log: (String) -> Void = { message in\n            logger?(\"[webview] \\(message)\")\n        }\n        let key = ObjectIdentifier(websiteDataStore)\n\n        if let entry = self.entries[key] {\n            if entry.isBusy {\n                log(\"Cached WebView busy; using a temporary WebView.\")\n                let (webView, host) = self.makeWebView(websiteDataStore: websiteDataStore)\n                host.show()\n                do {\n                    try await self.prepareWebView(webView, usageURL: usageURL, timeout: navigationTimeout)\n                } catch {\n                    host.close()\n                    throw error\n                }\n                return OpenAIDashboardWebViewLease(\n                    webView: webView,\n                    log: log,\n                    release: { host.close() })\n            }\n\n            entry.isBusy = true\n            entry.lastUsedAt = now\n            entry.host.show()\n            do {\n                try await self.prepareWebView(entry.webView, usageURL: usageURL, timeout: navigationTimeout)\n            } catch {\n                entry.isBusy = false\n                entry.lastUsedAt = Date()\n                entry.host.close()\n                self.entries.removeValue(forKey: key)\n                Self.log.warning(\"OpenAI webview prepare failed\")\n                throw error\n            }\n\n            return OpenAIDashboardWebViewLease(\n                webView: entry.webView,\n                log: log,\n                release: { [weak self, weak entry] in\n                    guard let self, let entry else { return }\n                    entry.isBusy = false\n                    entry.lastUsedAt = Date()\n                    // Hide instead of close - keep WebView cached for reuse.\n                    // This avoids re-downloading the ChatGPT SPA bundle on every refresh,\n                    // saving significant network bandwidth. See GitHub issues #269, #251.\n                    entry.host.hide()\n                    self.prune(now: Date())\n                })\n        }\n\n        let (webView, host) = self.makeWebView(websiteDataStore: websiteDataStore)\n        let entry = Entry(webView: webView, host: host, lastUsedAt: now, isBusy: true)\n        self.entries[key] = entry\n        host.show()\n\n        do {\n            try await self.prepareWebView(webView, usageURL: usageURL, timeout: navigationTimeout)\n        } catch {\n            self.entries.removeValue(forKey: key)\n            host.close()\n            Self.log.warning(\"OpenAI webview prepare failed\")\n            throw error\n        }\n\n        return OpenAIDashboardWebViewLease(\n            webView: webView,\n            log: log,\n            release: { [weak self, weak entry] in\n                guard let self, let entry else { return }\n                entry.isBusy = false\n                entry.lastUsedAt = Date()\n                // Hide instead of close - keep WebView cached for reuse.\n                // This avoids re-downloading the ChatGPT SPA bundle on every refresh,\n                // saving significant network bandwidth. See GitHub issues #269, #251.\n                entry.host.hide()\n                self.prune(now: Date())\n            })\n    }\n\n    func evict(websiteDataStore: WKWebsiteDataStore) {\n        let key = ObjectIdentifier(websiteDataStore)\n        guard let entry = self.entries.removeValue(forKey: key) else { return }\n        Self.log.debug(\"OpenAI webview evicted\")\n        entry.host.close()\n    }\n\n    private func prune(now: Date) {\n        let expired = self.entries.filter { _, entry in\n            !entry.isBusy && now.timeIntervalSince(entry.lastUsedAt) > self.idleTimeout\n        }\n        for (key, entry) in expired {\n            entry.host.close()\n            self.entries.removeValue(forKey: key)\n            Self.log.debug(\"OpenAI webview pruned\")\n        }\n    }\n\n    private func makeWebView(websiteDataStore: WKWebsiteDataStore) -> (WKWebView, OffscreenWebViewHost) {\n        let config = WKWebViewConfiguration()\n        config.websiteDataStore = websiteDataStore\n        if #available(macOS 14.0, *) {\n            config.preferences.inactiveSchedulingPolicy = .suspend\n        }\n\n        let webView = WKWebView(frame: .zero, configuration: config)\n        let host = OffscreenWebViewHost(webView: webView)\n        return (webView, host)\n    }\n\n    private func prepareWebView(_ webView: WKWebView, usageURL: URL, timeout: TimeInterval) async throws {\n        try await withCheckedThrowingContinuation { (cont: CheckedContinuation<Void, Error>) in\n            let delegate = NavigationDelegate { result in\n                cont.resume(with: result)\n            }\n            webView.navigationDelegate = delegate\n            webView.codexNavigationDelegate = delegate\n            delegate.armTimeout(seconds: timeout)\n            _ = webView.load(URLRequest(url: usageURL))\n        }\n    }\n}\n\n@MainActor\nprivate final class OffscreenWebViewHost {\n    private let window: NSWindow\n    private weak var webView: WKWebView?\n\n    init(webView: WKWebView) {\n        // WebKit throttles timers/RAF aggressively when a WKWebView is not considered \"visible\".\n        // The Codex usage page uses streaming SSR + client hydration; if RAF is throttled, the\n        // dashboard never becomes part of the visible DOM and `document.body.innerText` stays tiny.\n        //\n        // Keep a transparent (mouse-ignoring) window technically \"on-screen\" while scraping, but\n        // place it almost entirely off-screen so we never ghost-render dashboard UI over the desktop.\n        let visibleFrame = NSScreen.main?.visibleFrame ?? NSRect(x: 0, y: 0, width: 900, height: 700)\n        let frame = OpenAIDashboardFetcher.offscreenHostWindowFrame(for: visibleFrame)\n        let window = NSWindow(\n            contentRect: frame,\n            styleMask: [.borderless],\n            backing: .buffered,\n            defer: false)\n        window.isReleasedWhenClosed = false\n        window.backgroundColor = .clear\n        window.isOpaque = false\n        // Keep it effectively invisible, but non-zero alpha so WebKit treats it as \"visible\" and doesn't\n        // stall hydration (we've observed a head-only HTML shell for minutes at alpha=0).\n        window.alphaValue = OpenAIDashboardFetcher.offscreenHostAlphaValue()\n        window.hasShadow = false\n        window.ignoresMouseEvents = true\n        window.level = .floating\n        window.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]\n        window.isExcludedFromWindowsMenu = true\n        window.contentView = webView\n\n        self.window = window\n        self.webView = webView\n    }\n\n    func show() {\n        OpenAIDashboardWebViewCache.log.debug(\"OpenAI webview show\")\n        self.window.alphaValue = OpenAIDashboardFetcher.offscreenHostAlphaValue()\n        self.window.orderFrontRegardless()\n    }\n\n    func hide() {\n        // Set alpha to 0 so WebKit recognizes the page as inactive and applies\n        // its scheduling policy (throttle/suspend), reducing CPU when idle.\n        OpenAIDashboardWebViewCache.log.debug(\"OpenAI webview hide\")\n        self.window.alphaValue = 0.0\n        self.window.orderOut(nil)\n    }\n\n    func close() {\n        OpenAIDashboardWebViewCache.log.debug(\"OpenAI webview close\")\n        WebKitTeardown.scheduleCleanup(\n            owner: self,\n            window: self.window,\n            webView: self.webView,\n            closeWindow: { [window] in\n                window.orderOut(nil)\n                window.close()\n            })\n    }\n}\n\n#endif\n"
  },
  {
    "path": "Sources/CodexBarCore/OpenAIWeb/OpenAIDashboardWebsiteDataStore.swift",
    "content": "#if os(macOS)\nimport CryptoKit\nimport Foundation\nimport WebKit\n\n/// Per-account persistent `WKWebsiteDataStore` for the OpenAI dashboard scrape.\n///\n/// Why: `WKWebsiteDataStore.default()` is a single shared cookie jar. If the user switches Codex accounts,\n/// we want to keep multiple signed-in dashboard sessions around (one per email) without clearing cookies.\n///\n/// Implementation detail: macOS 14+ supports `WKWebsiteDataStore.dataStore(forIdentifier:)`, which creates\n/// persistent isolated stores keyed by an identifier. We derive a stable UUID from the email so the same\n/// account always maps to the same cookie store.\n///\n/// Important: We cache the `WKWebsiteDataStore` instances so the same object is returned for the same\n/// account email. This ensures `OpenAIDashboardWebViewCache` can use object identity for cache lookups.\n@MainActor\npublic enum OpenAIDashboardWebsiteDataStore {\n    /// Cached data store instances keyed by normalized email.\n    /// Using the same instance ensures stable object identity for WebView cache lookups.\n    private static var cachedStores: [String: WKWebsiteDataStore] = [:]\n\n    public static func store(forAccountEmail email: String?) -> WKWebsiteDataStore {\n        guard let normalized = normalizeEmail(email) else { return .default() }\n\n        // Return cached instance if available to maintain stable object identity\n        if let cached = cachedStores[normalized] {\n            return cached\n        }\n\n        let id = Self.identifier(forNormalizedEmail: normalized)\n        let store = WKWebsiteDataStore(forIdentifier: id)\n        self.cachedStores[normalized] = store\n        return store\n    }\n\n    /// Clears the persistent cookie store for a single account email.\n    ///\n    /// Note: this does *not* impact other accounts, and is safe to use when the stored session is \"stuck\"\n    /// or signed in to a different account than expected.\n    public static func clearStore(forAccountEmail email: String?) async {\n        // Clear only ChatGPT/OpenAI domain data for the per-account store.\n        // Avoid deleting the entire persistent store (WebKit requires all WKWebViews using it to be released).\n        let store = self.store(forAccountEmail: email)\n        await withCheckedContinuation { cont in\n            store.fetchDataRecords(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes()) { records in\n                let filtered = records.filter { record in\n                    let name = record.displayName.lowercased()\n                    return name.contains(\"chatgpt.com\") || name.contains(\"openai.com\")\n                }\n                store.removeData(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes(), for: filtered) {\n                    cont.resume()\n                }\n            }\n        }\n\n        // Remove from cache so a fresh instance is created on next access\n        if let normalized = normalizeEmail(email) {\n            self.cachedStores.removeValue(forKey: normalized)\n        }\n    }\n\n    #if DEBUG\n    /// Clear all cached store instances (for test isolation).\n    public static func clearCacheForTesting() {\n        self.cachedStores.removeAll()\n    }\n    #endif\n\n    // MARK: - Private\n\n    private static func normalizeEmail(_ email: String?) -> String? {\n        guard let raw = email?.trimmingCharacters(in: .whitespacesAndNewlines), !raw.isEmpty else { return nil }\n        return raw.lowercased()\n    }\n\n    private static func identifier(forNormalizedEmail email: String) -> UUID {\n        let digest = SHA256.hash(data: Data(email.utf8))\n        var bytes = Array(digest.prefix(16))\n\n        // Make it a well-formed UUID (v4 + RFC4122 variant) while staying deterministic.\n        bytes[6] = (bytes[6] & 0x0F) | 0x40\n        bytes[8] = (bytes[8] & 0x3F) | 0x80\n\n        let uuidBytes: uuid_t = (\n            bytes[0],\n            bytes[1],\n            bytes[2],\n            bytes[3],\n            bytes[4],\n            bytes[5],\n            bytes[6],\n            bytes[7],\n            bytes[8],\n            bytes[9],\n            bytes[10],\n            bytes[11],\n            bytes[12],\n            bytes[13],\n            bytes[14],\n            bytes[15])\n        return UUID(uuid: uuidBytes)\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/CodexBarCore/PathEnvironment.swift",
    "content": "import Foundation\n\npublic enum PathPurpose: Hashable, Sendable {\n    case rpc\n    case tty\n    case nodeTooling\n}\n\npublic struct PathDebugSnapshot: Equatable, Sendable {\n    public let codexBinary: String?\n    public let claudeBinary: String?\n    public let geminiBinary: String?\n    public let effectivePATH: String\n    public let loginShellPATH: String?\n\n    public static let empty = PathDebugSnapshot(\n        codexBinary: nil,\n        claudeBinary: nil,\n        geminiBinary: nil,\n        effectivePATH: \"\",\n        loginShellPATH: nil)\n\n    public init(\n        codexBinary: String?,\n        claudeBinary: String?,\n        geminiBinary: String? = nil,\n        effectivePATH: String,\n        loginShellPATH: String?)\n    {\n        self.codexBinary = codexBinary\n        self.claudeBinary = claudeBinary\n        self.geminiBinary = geminiBinary\n        self.effectivePATH = effectivePATH\n        self.loginShellPATH = loginShellPATH\n    }\n}\n\npublic enum BinaryLocator {\n    public static func resolveClaudeBinary(\n        env: [String: String] = ProcessInfo.processInfo.environment,\n        loginPATH: [String]? = LoginShellPathCache.shared.current,\n        commandV: (String, String?, TimeInterval, FileManager) -> String? = ShellCommandLocator.commandV,\n        aliasResolver: (String, String?, TimeInterval, FileManager, String) -> String? = ShellCommandLocator\n            .resolveAlias,\n        fileManager: FileManager = .default,\n        home: String = NSHomeDirectory()) -> String?\n    {\n        self.resolveBinary(\n            name: \"claude\",\n            overrideKey: \"CLAUDE_CLI_PATH\",\n            env: env,\n            loginPATH: loginPATH,\n            commandV: commandV,\n            aliasResolver: aliasResolver,\n            fileManager: fileManager,\n            home: home)\n    }\n\n    public static func resolveCodexBinary(\n        env: [String: String] = ProcessInfo.processInfo.environment,\n        loginPATH: [String]? = LoginShellPathCache.shared.current,\n        commandV: (String, String?, TimeInterval, FileManager) -> String? = ShellCommandLocator.commandV,\n        aliasResolver: (String, String?, TimeInterval, FileManager, String) -> String? = ShellCommandLocator\n            .resolveAlias,\n        fileManager: FileManager = .default,\n        home: String = NSHomeDirectory()) -> String?\n    {\n        self.resolveBinary(\n            name: \"codex\",\n            overrideKey: \"CODEX_CLI_PATH\",\n            env: env,\n            loginPATH: loginPATH,\n            commandV: commandV,\n            aliasResolver: aliasResolver,\n            fileManager: fileManager,\n            home: home)\n    }\n\n    public static func resolveGeminiBinary(\n        env: [String: String] = ProcessInfo.processInfo.environment,\n        loginPATH: [String]? = LoginShellPathCache.shared.current,\n        commandV: (String, String?, TimeInterval, FileManager) -> String? = ShellCommandLocator.commandV,\n        aliasResolver: (String, String?, TimeInterval, FileManager, String) -> String? = ShellCommandLocator\n            .resolveAlias,\n        fileManager: FileManager = .default,\n        home: String = NSHomeDirectory()) -> String?\n    {\n        self.resolveBinary(\n            name: \"gemini\",\n            overrideKey: \"GEMINI_CLI_PATH\",\n            env: env,\n            loginPATH: loginPATH,\n            commandV: commandV,\n            aliasResolver: aliasResolver,\n            fileManager: fileManager,\n            home: home)\n    }\n\n    public static func resolveAuggieBinary(\n        env: [String: String] = ProcessInfo.processInfo.environment,\n        loginPATH: [String]? = LoginShellPathCache.shared.current,\n        commandV: (String, String?, TimeInterval, FileManager) -> String? = ShellCommandLocator.commandV,\n        aliasResolver: (String, String?, TimeInterval, FileManager, String) -> String? = ShellCommandLocator\n            .resolveAlias,\n        fileManager: FileManager = .default,\n        home: String = NSHomeDirectory()) -> String?\n    {\n        self.resolveBinary(\n            name: \"auggie\",\n            overrideKey: \"AUGGIE_CLI_PATH\",\n            env: env,\n            loginPATH: loginPATH,\n            commandV: commandV,\n            aliasResolver: aliasResolver,\n            fileManager: fileManager,\n            home: home)\n    }\n\n    // swiftlint:disable function_parameter_count\n    private static func resolveBinary(\n        name: String,\n        overrideKey: String,\n        env: [String: String],\n        loginPATH: [String]?,\n        commandV: (String, String?, TimeInterval, FileManager) -> String?,\n        aliasResolver: (String, String?, TimeInterval, FileManager, String) -> String?,\n        fileManager: FileManager,\n        home: String) -> String?\n    {\n        // swiftlint:enable function_parameter_count\n        // 1) Explicit override\n        if let override = env[overrideKey], fileManager.isExecutableFile(atPath: override) {\n            return override\n        }\n\n        // 2) Login-shell PATH (captured once per launch)\n        if let loginPATH,\n           let pathHit = self.find(name, in: loginPATH, fileManager: fileManager)\n        {\n            return pathHit\n        }\n\n        // 3) Existing PATH\n        if let existingPATH = env[\"PATH\"],\n           let pathHit = self.find(\n               name,\n               in: existingPATH.split(separator: \":\").map(String.init),\n               fileManager: fileManager)\n        {\n            return pathHit\n        }\n\n        // 4) Interactive login shell lookup (captures nvm/fnm/mise paths from .zshrc/.bashrc)\n        if let shellHit = commandV(name, env[\"SHELL\"], 2.0, fileManager),\n           fileManager.isExecutableFile(atPath: shellHit)\n        {\n            return shellHit\n        }\n\n        // 4b) Alias fallback (login shell); only attempt after all standard lookups fail.\n        if let aliasHit = aliasResolver(name, env[\"SHELL\"], 2.0, fileManager, home),\n           fileManager.isExecutableFile(atPath: aliasHit)\n        {\n            return aliasHit\n        }\n\n        // 5) Minimal fallback\n        let fallback = [\"/usr/bin\", \"/bin\", \"/usr/sbin\", \"/sbin\"]\n        if let pathHit = self.find(name, in: fallback, fileManager: fileManager) {\n            return pathHit\n        }\n\n        return nil\n    }\n\n    private static func find(_ binary: String, in paths: [String], fileManager: FileManager) -> String? {\n        for path in paths where !path.isEmpty {\n            let candidate = \"\\(path.hasSuffix(\"/\") ? String(path.dropLast()) : path)/\\(binary)\"\n            if fileManager.isExecutableFile(atPath: candidate) {\n                return candidate\n            }\n        }\n        return nil\n    }\n}\n\npublic enum ShellCommandLocator {\n    public static func commandV(\n        _ tool: String,\n        _ shell: String?,\n        _ timeout: TimeInterval,\n        _ fileManager: FileManager) -> String?\n    {\n        let text = self.runShellCapture(shell, timeout, \"command -v \\(tool)\")?\n            .trimmingCharacters(in: .whitespacesAndNewlines)\n        guard let text, !text.isEmpty else { return nil }\n\n        let lines = text.split(separator: \"\\n\").map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }\n        for line in lines.reversed() where line.hasPrefix(\"/\") {\n            let path = line\n            if fileManager.isExecutableFile(atPath: path) {\n                return path\n            }\n        }\n\n        return nil\n    }\n\n    public static func resolveAlias(\n        _ tool: String,\n        _ shell: String?,\n        _ timeout: TimeInterval,\n        _ fileManager: FileManager,\n        _ home: String) -> String?\n    {\n        let command = \"alias \\(tool) 2>/dev/null; type -a \\(tool) 2>/dev/null\"\n        guard let text = self.runShellCapture(shell, timeout, command) else { return nil }\n        let lines = text.split(separator: \"\\n\").map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }\n\n        if let aliasPath = self.parseAliasPath(lines, tool: tool, home: home, fileManager: fileManager) {\n            return aliasPath\n        }\n\n        for line in lines {\n            if let path = self.extractPathCandidate(line: line, tool: tool, home: home),\n               fileManager.isExecutableFile(atPath: path)\n            {\n                return path\n            }\n        }\n\n        return nil\n    }\n\n    private static func runShellCapture(_ shell: String?, _ timeout: TimeInterval, _ command: String) -> String? {\n        let shellPath = (shell?.isEmpty == false) ? shell! : \"/bin/zsh\"\n        let isCI = [\"1\", \"true\"].contains(ProcessInfo.processInfo.environment[\"CI\"]?.lowercased())\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: shellPath)\n        // Interactive login shell to pick up PATH mutations from shell init (nvm/fnm/mise).\n        // CI runners can have shell init hooks that emit missing CLI errors; avoid them in CI.\n        process.arguments = isCI ? [\"-c\", command] : [\"-l\", \"-i\", \"-c\", command]\n        let stdout = Pipe()\n        process.standardOutput = stdout\n        process.standardError = Pipe()\n        do {\n            try process.run()\n        } catch {\n            return nil\n        }\n\n        let deadline = Date().addingTimeInterval(timeout)\n        while process.isRunning, Date() < deadline {\n            Thread.sleep(forTimeInterval: 0.05)\n        }\n\n        if process.isRunning {\n            process.terminate()\n            return nil\n        }\n\n        let data = stdout.fileHandleForReading.readDataToEndOfFile()\n        return String(data: data, encoding: .utf8)\n    }\n\n    private static func parseAliasPath(\n        _ lines: [String],\n        tool: String,\n        home: String,\n        fileManager: FileManager) -> String?\n    {\n        for line in lines {\n            if line.hasPrefix(\"alias \\(tool)=\") {\n                let value = line.replacingOccurrences(of: \"alias \\(tool)=\", with: \"\")\n                if let path = self.extractAliasExpansion(value, home: home),\n                   fileManager.isExecutableFile(atPath: path)\n                {\n                    return path\n                }\n            }\n            if line.lowercased().contains(\"aliased to\") {\n                if let range = line.range(of: \"aliased to\") {\n                    let value = line[range.upperBound...].trimmingCharacters(in: .whitespacesAndNewlines)\n                    if let path = self.extractAliasExpansion(String(value), home: home),\n                       fileManager.isExecutableFile(atPath: path)\n                    {\n                        return path\n                    }\n                }\n            }\n        }\n        return nil\n    }\n\n    private static func extractAliasExpansion(_ raw: String, home: String) -> String? {\n        let trimmed = raw.trimmingCharacters(in: CharacterSet(charactersIn: \" \\t\\\"'`\"))\n        guard !trimmed.isEmpty else { return nil }\n        let parts = trimmed.split(separator: \" \").map(String.init)\n        guard let first = parts.first else { return nil }\n        return self.expandPath(first, home: home)\n    }\n\n    private static func extractPathCandidate(line: String, tool: String, home: String) -> String? {\n        let tokens = line.split(whereSeparator: { $0 == \" \" || $0 == \"\\t\" }).map(String.init)\n        for token in tokens {\n            let candidate = self.expandPath(token, home: home)\n            if candidate.hasPrefix(\"/\"),\n               URL(fileURLWithPath: candidate).lastPathComponent == tool\n            {\n                return candidate\n            }\n        }\n        return nil\n    }\n\n    private static func expandPath(_ raw: String, home: String) -> String {\n        if raw == \"~\" { return home }\n        if raw.hasPrefix(\"~/\") { return home + String(raw.dropFirst()) }\n        return raw\n    }\n}\n\npublic enum PathBuilder {\n    public static func effectivePATH(\n        purposes _: Set<PathPurpose>,\n        env: [String: String] = ProcessInfo.processInfo.environment,\n        loginPATH: [String]? = LoginShellPathCache.shared.current,\n        home _: String = NSHomeDirectory()) -> String\n    {\n        var parts: [String] = []\n\n        if let loginPATH, !loginPATH.isEmpty {\n            parts.append(contentsOf: loginPATH)\n        }\n\n        if let existing = env[\"PATH\"], !existing.isEmpty {\n            parts.append(contentsOf: existing.split(separator: \":\").map(String.init))\n        }\n\n        if parts.isEmpty {\n            parts.append(contentsOf: [\"/usr/bin\", \"/bin\", \"/usr/sbin\", \"/sbin\"])\n        }\n\n        var seen = Set<String>()\n        let deduped = parts.compactMap { part -> String? in\n            guard !part.isEmpty else { return nil }\n            if seen.insert(part).inserted {\n                return part\n            }\n            return nil\n        }\n\n        return deduped.joined(separator: \":\")\n    }\n\n    public static func debugSnapshot(\n        purposes: Set<PathPurpose>,\n        env: [String: String] = ProcessInfo.processInfo.environment,\n        home: String = NSHomeDirectory()) -> PathDebugSnapshot\n    {\n        let login = LoginShellPathCache.shared.current\n        let effective = self.effectivePATH(\n            purposes: purposes,\n            env: env,\n            loginPATH: login,\n            home: home)\n        let codex = BinaryLocator.resolveCodexBinary(env: env, loginPATH: login, home: home)\n        let claude = BinaryLocator.resolveClaudeBinary(env: env, loginPATH: login, home: home)\n        let gemini = BinaryLocator.resolveGeminiBinary(env: env, loginPATH: login, home: home)\n        let loginString = login?.joined(separator: \":\")\n        return PathDebugSnapshot(\n            codexBinary: codex,\n            claudeBinary: claude,\n            geminiBinary: gemini,\n            effectivePATH: effective,\n            loginShellPATH: loginString)\n    }\n\n    public static func debugSnapshotAsync(\n        purposes: Set<PathPurpose>,\n        env: [String: String] = ProcessInfo.processInfo.environment,\n        home: String = NSHomeDirectory()) async -> PathDebugSnapshot\n    {\n        await Task.detached(priority: .userInitiated) {\n            self.debugSnapshot(purposes: purposes, env: env, home: home)\n        }.value\n    }\n}\n\nenum LoginShellPathCapturer {\n    static func capture(\n        shell: String? = ProcessInfo.processInfo.environment[\"SHELL\"],\n        timeout: TimeInterval = 2.0) -> [String]?\n    {\n        let shellPath = (shell?.isEmpty == false) ? shell! : \"/bin/zsh\"\n        let isCI = [\"1\", \"true\"].contains(ProcessInfo.processInfo.environment[\"CI\"]?.lowercased())\n        let marker = \"__CODEXBAR_PATH__\"\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: shellPath)\n        // Skip interactive login shells in CI to avoid noisy init hooks.\n        process.arguments = isCI\n            ? [\"-c\", \"printf '\\(marker)%s\\(marker)' \\\"$PATH\\\"\"]\n            : [\"-l\", \"-i\", \"-c\", \"printf '\\(marker)%s\\(marker)' \\\"$PATH\\\"\"]\n        let stdout = Pipe()\n        process.standardOutput = stdout\n        process.standardError = Pipe()\n        do {\n            try process.run()\n        } catch {\n            return nil\n        }\n\n        let deadline = Date().addingTimeInterval(timeout)\n        while process.isRunning, Date() < deadline {\n            Thread.sleep(forTimeInterval: 0.05)\n        }\n\n        if process.isRunning {\n            process.terminate()\n            return nil\n        }\n\n        let data = stdout.fileHandleForReading.readDataToEndOfFile()\n        guard let raw = String(data: data, encoding: .utf8),\n              !raw.isEmpty else { return nil }\n\n        let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)\n        let extracted = if let start = trimmed.range(of: marker),\n                           let end = trimmed.range(of: marker, options: .backwards),\n                           start.upperBound <= end.lowerBound\n        {\n            String(trimmed[start.upperBound..<end.lowerBound])\n        } else {\n            trimmed\n        }\n\n        let value = extracted.trimmingCharacters(in: .whitespacesAndNewlines)\n        guard !value.isEmpty else { return nil }\n        return value.split(separator: \":\").map(String.init)\n    }\n}\n\npublic final class LoginShellPathCache: @unchecked Sendable {\n    public static let shared = LoginShellPathCache()\n\n    private let lock = NSLock()\n    private var captured: [String]?\n    private var isCapturing = false\n    private var callbacks: [([String]?) -> Void] = []\n\n    public var current: [String]? {\n        self.lock.lock()\n        let value = self.captured\n        self.lock.unlock()\n        return value\n    }\n\n    public func captureOnce(\n        shell: String? = ProcessInfo.processInfo.environment[\"SHELL\"],\n        timeout: TimeInterval = 2.0,\n        onFinish: (([String]?) -> Void)? = nil)\n    {\n        self.lock.lock()\n        if let captured {\n            self.lock.unlock()\n            onFinish?(captured)\n            return\n        }\n\n        if let onFinish {\n            self.callbacks.append(onFinish)\n        }\n\n        if self.isCapturing {\n            self.lock.unlock()\n            return\n        }\n\n        self.isCapturing = true\n        self.lock.unlock()\n\n        DispatchQueue.global(qos: .utility).async { [weak self] in\n            let result = LoginShellPathCapturer.capture(shell: shell, timeout: timeout)\n            guard let self else { return }\n\n            self.lock.lock()\n            self.captured = result\n            self.isCapturing = false\n            let callbacks = self.callbacks\n            self.callbacks.removeAll()\n            self.lock.unlock()\n\n            callbacks.forEach { $0(result) }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/ProviderCostSnapshot.swift",
    "content": "import Foundation\n\n/// Provider-specific spend/budget snapshot (e.g. Claude \"Extra usage\" monthly spend vs limit).\npublic struct ProviderCostSnapshot: Equatable, Codable, Sendable {\n    public let used: Double\n    public let limit: Double\n    public let currencyCode: String\n    /// Human-friendly period label (e.g. \"Monthly\"). Optional; some providers don't expose a period.\n    public let period: String?\n    /// Optional renewal/reset timestamp for the period.\n    public let resetsAt: Date?\n    public let updatedAt: Date\n\n    public init(\n        used: Double,\n        limit: Double,\n        currencyCode: String,\n        period: String? = nil,\n        resetsAt: Date? = nil,\n        updatedAt: Date)\n    {\n        self.used = used\n        self.limit = limit\n        self.currencyCode = currencyCode\n        self.period = period\n        self.resetsAt = resetsAt\n        self.updatedAt = updatedAt\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Alibaba/AlibabaCodingPlanAPIRegion.swift",
    "content": "import Foundation\n\npublic enum AlibabaCodingPlanAPIRegion: String, CaseIterable, Sendable {\n    case international = \"intl\"\n    case chinaMainland = \"cn\"\n\n    private static let endpointPath = \"data/api.json\"\n\n    public var displayName: String {\n        switch self {\n        case .international:\n            \"International (modelstudio.console.alibabacloud.com)\"\n        case .chinaMainland:\n            \"China mainland (bailian.console.aliyun.com)\"\n        }\n    }\n\n    public var gatewayBaseURLString: String {\n        switch self {\n        case .international:\n            \"https://modelstudio.console.alibabacloud.com\"\n        case .chinaMainland:\n            \"https://bailian.console.aliyun.com\"\n        }\n    }\n\n    public var dashboardURL: URL {\n        switch self {\n        case .international:\n            URL(string: \"https://modelstudio.console.alibabacloud.com/ap-southeast-1/?tab=coding-plan#/efm/detail\")!\n        case .chinaMainland:\n            URL(string: \"https://bailian.console.aliyun.com/cn-beijing/?tab=model#/efm/coding_plan\")!\n        }\n    }\n\n    public var consoleDomain: String {\n        switch self {\n        case .international:\n            \"modelstudio.console.alibabacloud.com\"\n        case .chinaMainland:\n            \"bailian.console.aliyun.com\"\n        }\n    }\n\n    public var consoleSite: String {\n        switch self {\n        case .international:\n            \"MODELSTUDIO_ALIBABACLOUD\"\n        case .chinaMainland:\n            \"BAILIAN_CONSOLE\"\n        }\n    }\n\n    public var consoleRefererURL: URL {\n        switch self {\n        case .international:\n            URL(string: \"https://modelstudio.console.alibabacloud.com/ap-southeast-1/?tab=coding-plan\")!\n        case .chinaMainland:\n            URL(string: \"https://bailian.console.aliyun.com/cn-beijing/?tab=model\")!\n        }\n    }\n\n    public var quotaURL: URL {\n        var components = URLComponents(string: self.gatewayBaseURLString)!\n        components.path = \"/\" + Self.endpointPath\n        components.queryItems = [\n            URLQueryItem(\n                name: \"action\",\n                value: \"zeldaEasy.broadscope-bailian.codingPlan.queryCodingPlanInstanceInfoV2\"),\n            URLQueryItem(name: \"product\", value: \"broadscope-bailian\"),\n            URLQueryItem(name: \"api\", value: \"queryCodingPlanInstanceInfoV2\"),\n            URLQueryItem(name: \"currentRegionId\", value: self.currentRegionID),\n        ]\n        return components.url!\n    }\n\n    public var consoleRPCBaseURLString: String {\n        switch self {\n        case .international:\n            \"https://bailian-singapore-cs.alibabacloud.com\"\n        case .chinaMainland:\n            \"https://bailian-beijing-cs.aliyuncs.com\"\n        }\n    }\n\n    public var consoleRPCURL: URL {\n        var components = URLComponents(string: self.consoleRPCBaseURLString)!\n        components.path = \"/\" + Self.endpointPath\n        components.queryItems = [\n            URLQueryItem(name: \"action\", value: self.consoleRPCAction),\n            URLQueryItem(name: \"product\", value: self.consoleRPCProduct),\n            URLQueryItem(name: \"api\", value: self.consoleQuotaAPIName),\n            URLQueryItem(name: \"_v\", value: \"undefined\"),\n        ]\n        return components.url!\n    }\n\n    public var consoleRPCAction: String {\n        switch self {\n        case .international:\n            \"IntlBroadScopeAspnGateway\"\n        case .chinaMainland:\n            \"BroadScopeAspnGateway\"\n        }\n    }\n\n    public var consoleRPCProduct: String {\n        \"sfm_bailian\"\n    }\n\n    public var consoleQuotaAPIName: String {\n        \"zeldaEasy.broadscope-bailian.codingPlan.queryCodingPlanInstanceInfoV2\"\n    }\n\n    public var commodityCode: String {\n        switch self {\n        case .international:\n            \"sfm_codingplan_public_intl\"\n        case .chinaMainland:\n            \"sfm_codingplan_public_cn\"\n        }\n    }\n\n    public var currentRegionID: String {\n        switch self {\n        case .international:\n            \"ap-southeast-1\"\n        case .chinaMainland:\n            \"cn-beijing\"\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Alibaba/AlibabaCodingPlanCookieImporter.swift",
    "content": "import Foundation\n\n#if os(macOS)\nimport CommonCrypto\nimport Security\nimport SQLite3\nimport SweetCookieKit\n\nprivate let alibabaCookieImportOrder: BrowserCookieImportOrder =\n    ProviderDefaults.metadata[.alibaba]?.browserCookieOrder ?? Browser.defaultImportOrder\n\npublic enum AlibabaCodingPlanCookieImporter {\n    private static let cookieClient = BrowserCookieClient()\n    private static let cookieDomains = [\n        \"bailian-singapore-cs.alibabacloud.com\",\n        \"bailian-beijing-cs.aliyuncs.com\",\n        \"modelstudio.console.alibabacloud.com\",\n        \"bailian.console.aliyun.com\",\n        \"free.aliyun.com\",\n        \"account.aliyun.com\",\n        \"signin.aliyun.com\",\n        \"passport.alibabacloud.com\",\n        \"console.alibabacloud.com\",\n        \"console.aliyun.com\",\n        \"alibabacloud.com\",\n        \"aliyun.com\",\n    ]\n\n    public struct SessionInfo: Sendable {\n        public let cookies: [HTTPCookie]\n        public let sourceLabel: String\n\n        public init(cookies: [HTTPCookie], sourceLabel: String) {\n            self.cookies = cookies\n            self.sourceLabel = sourceLabel\n        }\n\n        public var cookieHeader: String {\n            var byName: [String: HTTPCookie] = [:]\n            byName.reserveCapacity(self.cookies.count)\n\n            for cookie in self.cookies {\n                if let expiry = cookie.expiresDate, expiry < Date() {\n                    continue\n                }\n                guard !cookie.value.isEmpty else { continue }\n                if let existing = byName[cookie.name] {\n                    let existingExpiry = existing.expiresDate ?? .distantPast\n                    let candidateExpiry = cookie.expiresDate ?? .distantPast\n                    if candidateExpiry >= existingExpiry {\n                        byName[cookie.name] = cookie\n                    }\n                } else {\n                    byName[cookie.name] = cookie\n                }\n            }\n\n            return byName.keys.sorted().compactMap { name in\n                guard let cookie = byName[name] else { return nil }\n                return \"\\(cookie.name)=\\(cookie.value)\"\n            }.joined(separator: \"; \")\n        }\n    }\n\n    nonisolated(unsafe) static var importSessionOverrideForTesting:\n        ((BrowserDetection, ((String) -> Void)?) throws -> SessionInfo)?\n\n    public static func importSession(\n        browserDetection: BrowserDetection,\n        logger: ((String) -> Void)? = nil) throws -> SessionInfo\n    {\n        if let override = self.importSessionOverrideForTesting {\n            return try override(browserDetection, logger)\n        }\n        let log: (String) -> Void = { msg in logger?(\"[alibaba-cookie] \\(msg)\") }\n        var accessDeniedHints: [String] = []\n        var failureDetails: [String] = []\n        let installedBrowsers = self.cookieImportCandidates(browserDetection: browserDetection)\n        log(\"Cookie import candidates: \\(installedBrowsers.map(\\.displayName).joined(separator: \", \"))\")\n\n        for browserSource in installedBrowsers {\n            do {\n                log(\"Checking \\(browserSource.displayName)\")\n                let query = BrowserCookieQuery(domains: self.cookieDomains)\n                let sources = try Self.cookieClient.records(\n                    matching: query,\n                    in: browserSource,\n                    logger: log)\n                if sources.isEmpty {\n                    log(\"No matching cookie records in \\(browserSource.displayName)\")\n                    if let fallbackSession = try Self.importChromiumFallbackSession(\n                        browser: browserSource,\n                        logger: log)\n                    {\n                        return fallbackSession\n                    }\n                }\n                for source in sources where !source.records.isEmpty {\n                    let httpCookies = BrowserCookieClient.makeHTTPCookies(source.records, origin: query.origin)\n                    if self.isAuthenticatedSession(cookies: httpCookies) {\n                        log(\"Found \\(httpCookies.count) Alibaba cookies in \\(source.label)\")\n                        return SessionInfo(cookies: httpCookies, sourceLabel: source.label)\n                    }\n                    let cookieNames = Set(httpCookies.map(\\.name))\n                    let hasTicket = cookieNames.contains(\"login_aliyunid_ticket\")\n                    let hasAccount =\n                        cookieNames.contains(\"login_aliyunid_pk\") ||\n                        cookieNames.contains(\"login_current_pk\") ||\n                        cookieNames.contains(\"login_aliyunid\")\n                    log(\"Skipping \\(source.label): missing auth cookies (ticket=\\(hasTicket), account=\\(hasAccount))\")\n                }\n                if let fallbackSession = try Self.importChromiumFallbackSession(browser: browserSource, logger: log) {\n                    return fallbackSession\n                }\n            } catch let error as BrowserCookieError {\n                BrowserCookieAccessGate.recordIfNeeded(error)\n                if let hint = error.accessDeniedHint {\n                    accessDeniedHints.append(hint)\n                }\n                failureDetails.append(\"\\(browserSource.displayName): \\(error.localizedDescription)\")\n                log(\"\\(browserSource.displayName) cookie import failed: \\(error.localizedDescription)\")\n            } catch {\n                failureDetails.append(\"\\(browserSource.displayName): \\(error.localizedDescription)\")\n                log(\"\\(browserSource.displayName) cookie import failed: \\(error.localizedDescription)\")\n            }\n        }\n\n        let details = (Array(Set(accessDeniedHints)).sorted() + Array(Set(failureDetails)).sorted())\n            .joined(separator: \" \")\n        throw AlibabaCodingPlanSettingsError.missingCookie(details: details.isEmpty ? nil : details)\n    }\n\n    private static func isAuthenticatedSession(cookies: [HTTPCookie]) -> Bool {\n        guard !cookies.isEmpty else { return false }\n        let names = Set(cookies.map(\\.name))\n        let hasTicket = names.contains(\"login_aliyunid_ticket\")\n        let hasAccount =\n            names.contains(\"login_aliyunid_pk\") ||\n            names.contains(\"login_current_pk\") ||\n            names.contains(\"login_aliyunid\")\n        return hasTicket && hasAccount\n    }\n\n    public static func hasSession(\n        browserDetection: BrowserDetection,\n        logger: ((String) -> Void)? = nil) -> Bool\n    {\n        do {\n            _ = try self.importSession(browserDetection: browserDetection, logger: logger)\n            return true\n        } catch {\n            return false\n        }\n    }\n\n    private static func importChromiumFallbackSession(\n        browser: Browser,\n        logger: ((String) -> Void)? = nil) throws -> SessionInfo?\n    {\n        guard browser.usesChromiumProfileStore else { return nil }\n        return try AlibabaChromiumCookieFallbackImporter.importSession(\n            browser: browser,\n            domains: self.cookieDomains,\n            logger: logger)\n    }\n\n    static func cookieImportCandidates(\n        browserDetection: BrowserDetection,\n        importOrder: BrowserCookieImportOrder = alibabaCookieImportOrder) -> [Browser]\n    {\n        importOrder.cookieImportCandidates(using: browserDetection)\n    }\n\n    static func matchesCookieDomain(_ domain: String, patterns: [String] = Self.cookieDomains) -> Bool {\n        let normalized = self.normalizeCookieDomain(domain)\n        return patterns.contains { pattern in\n            let normalizedPattern = self.normalizeCookieDomain(pattern)\n            return normalized == normalizedPattern || normalized.hasSuffix(\".\\(normalizedPattern)\")\n        }\n    }\n\n    static func normalizeCookieDomain(_ domain: String) -> String {\n        let trimmed = domain.trimmingCharacters(in: .whitespacesAndNewlines)\n        let normalized = trimmed.hasPrefix(\".\") ? String(trimmed.dropFirst()) : trimmed\n        return normalized.lowercased()\n    }\n}\n\nprivate enum AlibabaChromiumCookieFallbackImporter {\n    private struct ChromiumCookieRecord {\n        let domain: String\n        let name: String\n        let path: String\n        let value: String\n        let expires: Date?\n        let isSecure: Bool\n    }\n\n    enum ImportError: LocalizedError {\n        case keyUnavailable(browser: Browser)\n        case keychainDenied(browser: Browser)\n        case sqliteFailed(label: String, details: String)\n\n        var errorDescription: String? {\n            switch self {\n            case let .keyUnavailable(browser):\n                \"\\(browser.displayName) Safe Storage key not found.\"\n            case let .keychainDenied(browser):\n                \"macOS Keychain denied access to \\(browser.displayName) Safe Storage.\"\n            case let .sqliteFailed(label, details):\n                \"\\(label) cookie fallback failed: \\(details)\"\n            }\n        }\n    }\n\n    static func importSession(\n        browser: Browser,\n        domains: [String],\n        logger: ((String) -> Void)? = nil) throws -> AlibabaCodingPlanCookieImporter.SessionInfo?\n    {\n        let stores = BrowserCookieClient().stores(for: browser).filter { $0.databaseURL != nil }\n        guard !stores.isEmpty else { return nil }\n\n        logger?(\"[alibaba-cookie] Trying \\(browser.displayName) Chromium fallback\")\n        let keys = try self.derivedKeys(for: browser)\n        for store in stores {\n            let cookies = try self.loadCookies(from: store, domains: domains, keys: keys)\n            guard !cookies.isEmpty else { continue }\n            if self.isAuthenticatedSession(cookies) {\n                logger?(\"[alibaba-cookie] Found \\(cookies.count) Alibaba cookies via \\(store.label) fallback\")\n                return AlibabaCodingPlanCookieImporter.SessionInfo(cookies: cookies, sourceLabel: store.label)\n            }\n        }\n        return nil\n    }\n\n    private static func isAuthenticatedSession(_ cookies: [HTTPCookie]) -> Bool {\n        let names = Set(cookies.map(\\.name))\n        let hasTicket = names.contains(\"login_aliyunid_ticket\")\n        let hasAccount =\n            names.contains(\"login_aliyunid_pk\") ||\n            names.contains(\"login_current_pk\") ||\n            names.contains(\"login_aliyunid\")\n        return hasTicket && hasAccount\n    }\n\n    private static func loadCookies(\n        from store: BrowserCookieStore,\n        domains: [String],\n        keys: [Data]) throws -> [HTTPCookie]\n    {\n        guard let sourceDB = store.databaseURL else { return [] }\n        let records = try self.readCookiesFromLockedDB(\n            sourceDB: sourceDB,\n            domains: domains,\n            keys: keys,\n            label: store.label)\n        return records.compactMap(self.makeCookie)\n    }\n\n    private static func readCookiesFromLockedDB(\n        sourceDB: URL,\n        domains: [String],\n        keys: [Data],\n        label: String) throws -> [ChromiumCookieRecord]\n    {\n        let tempDir = FileManager.default.temporaryDirectory\n            .appendingPathComponent(\"alibaba-chromium-cookies-\\(UUID().uuidString)\", isDirectory: true)\n        try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n\n        let copiedDB = tempDir.appendingPathComponent(\"Cookies\")\n        try FileManager.default.copyItem(at: sourceDB, to: copiedDB)\n        for suffix in [\"-wal\", \"-shm\"] {\n            let src = URL(fileURLWithPath: sourceDB.path + suffix)\n            if FileManager.default.fileExists(atPath: src.path) {\n                let dst = URL(fileURLWithPath: copiedDB.path + suffix)\n                try? FileManager.default.copyItem(at: src, to: dst)\n            }\n        }\n        defer { try? FileManager.default.removeItem(at: tempDir) }\n\n        return try self.readCookies(fromDB: copiedDB.path, domains: domains, keys: keys, label: label)\n    }\n\n    private static func readCookies(\n        fromDB path: String,\n        domains: [String],\n        keys: [Data],\n        label: String) throws -> [ChromiumCookieRecord]\n    {\n        var db: OpaquePointer?\n        guard sqlite3_open_v2(path, &db, SQLITE_OPEN_READONLY, nil) == SQLITE_OK else {\n            throw ImportError.sqliteFailed(label: label, details: String(cString: sqlite3_errmsg(db)))\n        }\n        defer { sqlite3_close(db) }\n\n        let sql = \"SELECT host_key, name, path, expires_utc, is_secure, value, encrypted_value FROM cookies\"\n        var stmt: OpaquePointer?\n        guard sqlite3_prepare_v2(db, sql, -1, &stmt, nil) == SQLITE_OK else {\n            throw ImportError.sqliteFailed(label: label, details: String(cString: sqlite3_errmsg(db)))\n        }\n        defer { sqlite3_finalize(stmt) }\n\n        var records: [ChromiumCookieRecord] = []\n        while sqlite3_step(stmt) == SQLITE_ROW {\n            guard let hostKey = self.readText(stmt, index: 0), self.matches(domain: hostKey, patterns: domains) else {\n                continue\n            }\n            guard let name = self.readText(stmt, index: 1), let path = self.readText(stmt, index: 2) else {\n                continue\n            }\n\n            let value: String? = if let plain = self.readText(stmt, index: 5), !plain.isEmpty {\n                plain\n            } else if let encrypted = self.readBlob(stmt, index: 6) {\n                self.decrypt(encrypted, usingAnyOf: keys)\n            } else {\n                nil\n            }\n            guard let value, !value.isEmpty else { continue }\n\n            records.append(ChromiumCookieRecord(\n                domain: AlibabaCodingPlanCookieImporter.normalizeCookieDomain(hostKey),\n                name: name,\n                path: path,\n                value: value,\n                expires: self.chromiumExpiry(sqlite3_column_int64(stmt, 3)),\n                isSecure: sqlite3_column_int(stmt, 4) != 0))\n        }\n\n        return records.filter { record in\n            guard let expires = record.expires else { return true }\n            return expires >= Date()\n        }\n    }\n\n    private static func derivedKeys(for browser: Browser) throws -> [Data] {\n        var keys: [Data] = []\n        var sawDenied = false\n\n        for label in browser.safeStorageLabels {\n            switch KeychainAccessPreflight.checkGenericPassword(service: label.service, account: label.account) {\n            case .interactionRequired:\n                sawDenied = true\n                continue\n            case .allowed, .notFound, .failure:\n                break\n            }\n\n            if let password = self.safeStoragePassword(service: label.service, account: label.account) {\n                keys.append(self.deriveKey(from: password))\n            }\n        }\n\n        if !keys.isEmpty {\n            return keys\n        }\n        if sawDenied {\n            throw ImportError.keychainDenied(browser: browser)\n        }\n        throw ImportError.keyUnavailable(browser: browser)\n    }\n\n    private static func safeStoragePassword(service: String, account: String) -> String? {\n        let query: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: service,\n            kSecAttrAccount as String: account,\n            kSecMatchLimit as String: kSecMatchLimitOne,\n            kSecReturnData as String: true,\n        ]\n\n        var result: AnyObject?\n        let status = SecItemCopyMatching(query as CFDictionary, &result)\n        guard status == errSecSuccess, let data = result as? Data else { return nil }\n        return String(data: data, encoding: .utf8)\n    }\n\n    private static func deriveKey(from password: String) -> Data {\n        let salt = Data(\"saltysalt\".utf8)\n        var key = Data(count: kCCKeySizeAES128)\n        let keyLength = key.count\n        _ = key.withUnsafeMutableBytes { keyBytes in\n            password.utf8CString.withUnsafeBytes { passBytes in\n                salt.withUnsafeBytes { saltBytes in\n                    CCKeyDerivationPBKDF(\n                        CCPBKDFAlgorithm(kCCPBKDF2),\n                        passBytes.bindMemory(to: Int8.self).baseAddress,\n                        passBytes.count - 1,\n                        saltBytes.bindMemory(to: UInt8.self).baseAddress,\n                        salt.count,\n                        CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA1),\n                        1003,\n                        keyBytes.bindMemory(to: UInt8.self).baseAddress,\n                        keyLength)\n                }\n            }\n        }\n        return key\n    }\n\n    private static func decrypt(_ encryptedValue: Data, usingAnyOf keys: [Data]) -> String? {\n        for key in keys {\n            if let value = self.decrypt(encryptedValue, key: key) {\n                return value\n            }\n        }\n        return nil\n    }\n\n    private static func decrypt(_ encryptedValue: Data, key: Data) -> String? {\n        guard encryptedValue.count > 3 else { return nil }\n        let prefix = String(data: encryptedValue.prefix(3), encoding: .utf8)\n        guard prefix == \"v10\" else { return nil }\n\n        let payload = Data(encryptedValue.dropFirst(3))\n        let iv = Data(repeating: 0x20, count: kCCBlockSizeAES128)\n        var outLength = 0\n        var out = Data(count: payload.count + kCCBlockSizeAES128)\n        let outCapacity = out.count\n\n        let status = out.withUnsafeMutableBytes { outBytes in\n            payload.withUnsafeBytes { payloadBytes in\n                key.withUnsafeBytes { keyBytes in\n                    iv.withUnsafeBytes { ivBytes in\n                        CCCrypt(\n                            CCOperation(kCCDecrypt),\n                            CCAlgorithm(kCCAlgorithmAES),\n                            CCOptions(kCCOptionPKCS7Padding),\n                            keyBytes.baseAddress,\n                            key.count,\n                            ivBytes.baseAddress,\n                            payloadBytes.baseAddress,\n                            payload.count,\n                            outBytes.baseAddress,\n                            outCapacity,\n                            &outLength)\n                    }\n                }\n            }\n        }\n\n        guard status == kCCSuccess else { return nil }\n        out.count = outLength\n\n        if let value = String(data: out, encoding: .utf8), !value.isEmpty {\n            return value\n        }\n        if out.count > 32 {\n            let trimmed = out.dropFirst(32)\n            if let value = String(data: trimmed, encoding: .utf8), !value.isEmpty {\n                return value\n            }\n        }\n        return nil\n    }\n\n    private static func makeCookie(from record: ChromiumCookieRecord) -> HTTPCookie? {\n        var properties: [HTTPCookiePropertyKey: Any] = [\n            .domain: record.domain,\n            .path: record.path,\n            .name: record.name,\n            .value: record.value,\n        ]\n        if record.isSecure {\n            properties[.secure] = true\n        }\n        if let expires = record.expires {\n            properties[.expires] = expires\n        }\n        return HTTPCookie(properties: properties)\n    }\n\n    private static func readText(_ stmt: OpaquePointer?, index: Int32) -> String? {\n        guard sqlite3_column_type(stmt, index) != SQLITE_NULL,\n              let value = sqlite3_column_text(stmt, index)\n        else {\n            return nil\n        }\n        return String(cString: value)\n    }\n\n    private static func readBlob(_ stmt: OpaquePointer?, index: Int32) -> Data? {\n        guard sqlite3_column_type(stmt, index) != SQLITE_NULL,\n              let bytes = sqlite3_column_blob(stmt, index)\n        else {\n            return nil\n        }\n        return Data(bytes: bytes, count: Int(sqlite3_column_bytes(stmt, index)))\n    }\n\n    private static func matches(domain: String, patterns: [String]) -> Bool {\n        AlibabaCodingPlanCookieImporter.matchesCookieDomain(domain, patterns: patterns)\n    }\n\n    private static func chromiumExpiry(_ expiresUTC: Int64) -> Date? {\n        guard expiresUTC > 0 else { return nil }\n        let seconds = (Double(expiresUTC) / 1_000_000.0) - 11_644_473_600.0\n        guard seconds > 0 else { return nil }\n        return Date(timeIntervalSince1970: seconds)\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Alibaba/AlibabaCodingPlanProviderDescriptor.swift",
    "content": "import CodexBarMacroSupport\nimport Foundation\n\n#if os(macOS)\nimport SweetCookieKit\n#endif\n\n@ProviderDescriptorRegistration\n@ProviderDescriptorDefinition\npublic enum AlibabaCodingPlanProviderDescriptor {\n    static func makeDescriptor() -> ProviderDescriptor {\n        #if os(macOS)\n        let browserOrder: BrowserCookieImportOrder = [\n            .chrome,\n            .chromeBeta,\n            .brave,\n            .edge,\n            .arc,\n            .firefox,\n            .safari,\n        ]\n        #else\n        let browserOrder: BrowserCookieImportOrder? = nil\n        #endif\n\n        return ProviderDescriptor(\n            id: .alibaba,\n            metadata: ProviderMetadata(\n                id: .alibaba,\n                displayName: \"Alibaba\",\n                sessionLabel: \"5-hour\",\n                weeklyLabel: \"Weekly\",\n                opusLabel: \"Monthly\",\n                supportsOpus: true,\n                supportsCredits: false,\n                creditsHint: \"\",\n                toggleTitle: \"Show Alibaba usage\",\n                cliName: \"alibaba-coding-plan\",\n                defaultEnabled: false,\n                isPrimaryProvider: false,\n                usesAccountFallback: false,\n                browserCookieOrder: browserOrder,\n                dashboardURL: AlibabaCodingPlanAPIRegion.international.dashboardURL.absoluteString,\n                statusPageURL: nil,\n                statusLinkURL: \"https://status.aliyun.com\"),\n            branding: ProviderBranding(\n                iconStyle: .alibaba,\n                iconResourceName: \"ProviderIcon-alibaba\",\n                color: ProviderColor(red: 1.0, green: 106 / 255, blue: 0)),\n            tokenCost: ProviderTokenCostConfig(\n                supportsTokenCost: false,\n                noDataMessage: { \"Alibaba Coding Plan cost summary is not supported.\" }),\n            fetchPlan: ProviderFetchPlan(\n                sourceModes: [.auto, .web, .api],\n                pipeline: ProviderFetchPipeline(resolveStrategies: self.resolveStrategies)),\n            cli: ProviderCLIConfig(\n                name: \"alibaba-coding-plan\",\n                aliases: [\"alibaba\", \"bailian\"],\n                versionDetector: nil))\n    }\n\n    private static func resolveStrategies(context: ProviderFetchContext) async -> [any ProviderFetchStrategy] {\n        switch context.sourceMode {\n        case .web:\n            return [AlibabaCodingPlanWebFetchStrategy()]\n        case .api:\n            return [AlibabaCodingPlanAPIFetchStrategy()]\n        case .cli, .oauth:\n            return []\n        case .auto:\n            break\n        }\n\n        if context.settings?.alibaba?.cookieSource == .off {\n            return [AlibabaCodingPlanAPIFetchStrategy()]\n        }\n\n        return [AlibabaCodingPlanWebFetchStrategy(), AlibabaCodingPlanAPIFetchStrategy()]\n    }\n}\n\nstruct AlibabaCodingPlanWebFetchStrategy: ProviderFetchStrategy {\n    let id: String = \"alibaba-coding-plan.web\"\n    let kind: ProviderFetchKind = .web\n\n    func isAvailable(_ context: ProviderFetchContext) async -> Bool {\n        guard context.settings?.alibaba?.cookieSource != .off else { return false }\n\n        if AlibabaCodingPlanSettingsReader.cookieHeader(environment: context.env) != nil {\n            return true\n        }\n\n        if let settings = context.settings?.alibaba,\n           settings.cookieSource == .manual\n        {\n            return CookieHeaderNormalizer.normalize(settings.manualCookieHeader) != nil\n        }\n\n        #if os(macOS)\n        if let cached = CookieHeaderCache.load(provider: .alibaba),\n           !cached.cookieHeader.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty\n        {\n            return true\n        }\n        if AlibabaCodingPlanCookieImporter.hasSession(browserDetection: context.browserDetection) {\n            return true\n        }\n        return false\n        #else\n        return false\n        #endif\n    }\n\n    func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {\n        let cookieSource = context.settings?.alibaba?.cookieSource ?? .auto\n        let cookieHeader = try Self.resolveCookieHeader(context: context, allowCached: true)\n        do {\n            let region = context.settings?.alibaba?.apiRegion ?? .international\n            let usage = try await AlibabaCodingPlanUsageFetcher.fetchUsage(\n                cookieHeader: cookieHeader,\n                region: region,\n                environment: context.env)\n            return self.makeResult(usage: usage.toUsageSnapshot(), sourceLabel: \"web\")\n        } catch let error as AlibabaCodingPlanUsageError\n            where (error == .invalidCredentials || error == .loginRequired) && cookieSource != .manual\n        {\n            #if os(macOS)\n            CookieHeaderCache.clear(provider: .alibaba)\n            let refreshedHeader = try Self.resolveCookieHeader(context: context, allowCached: false)\n            let region = context.settings?.alibaba?.apiRegion ?? .international\n            let usage = try await AlibabaCodingPlanUsageFetcher.fetchUsage(\n                cookieHeader: refreshedHeader,\n                region: region,\n                environment: context.env)\n            return self.makeResult(usage: usage.toUsageSnapshot(), sourceLabel: \"web\")\n            #else\n            throw AlibabaCodingPlanUsageError.invalidCredentials\n            #endif\n        }\n    }\n\n    func shouldFallback(on error: Error, context: ProviderFetchContext) -> Bool {\n        guard context.sourceMode == .auto else { return false }\n\n        if let urlError = error as? URLError {\n            switch urlError.code {\n            case .timedOut,\n                 .cannotFindHost,\n                 .cannotConnectToHost,\n                 .networkConnectionLost,\n                 .dnsLookupFailed,\n                 .secureConnectionFailed,\n                 .serverCertificateHasBadDate,\n                 .serverCertificateUntrusted,\n                 .serverCertificateHasUnknownRoot,\n                 .serverCertificateNotYetValid,\n                 .clientCertificateRejected,\n                 .clientCertificateRequired,\n                 .cannotLoadFromNetwork,\n                 .internationalRoamingOff,\n                 .callIsActive,\n                 .dataNotAllowed,\n                 .requestBodyStreamExhausted,\n                 .resourceUnavailable,\n                 .notConnectedToInternet:\n                return true\n            default:\n                break\n            }\n        }\n\n        if let settingsError = error as? AlibabaCodingPlanSettingsError {\n            switch settingsError {\n            case .missingCookie, .invalidCookie:\n                return true\n            case .missingToken:\n                return false\n            }\n        }\n\n        guard let alibabaError = error as? AlibabaCodingPlanUsageError else { return false }\n        switch alibabaError {\n        case .loginRequired:\n            return true\n        case .invalidCredentials:\n            return true\n        case let .apiError(message):\n            return message.contains(\"HTTP 404\") || message.contains(\"HTTP 403\")\n        case .networkError:\n            return true\n        case .parseFailed:\n            return false\n        }\n    }\n\n    static func resolveCookieHeader(context: ProviderFetchContext, allowCached: Bool) throws -> String {\n        if let settings = context.settings?.alibaba,\n           settings.cookieSource == .manual\n        {\n            guard let header = CookieHeaderNormalizer.normalize(settings.manualCookieHeader) else {\n                throw AlibabaCodingPlanSettingsError.invalidCookie\n            }\n            return header\n        }\n\n        if let envCookie = AlibabaCodingPlanSettingsReader.cookieHeader(environment: context.env),\n           let normalized = CookieHeaderNormalizer.normalize(envCookie)\n        {\n            return normalized\n        }\n\n        #if os(macOS)\n        if allowCached,\n           let cached = CookieHeaderCache.load(provider: .alibaba),\n           !cached.cookieHeader.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty\n        {\n            return cached.cookieHeader\n        }\n\n        do {\n            let session = try AlibabaCodingPlanCookieImporter.importSession(browserDetection: context.browserDetection)\n            CookieHeaderCache.store(\n                provider: .alibaba,\n                cookieHeader: session.cookieHeader,\n                sourceLabel: session.sourceLabel)\n            return session.cookieHeader\n        } catch {\n            throw error\n        }\n        #else\n        throw AlibabaCodingPlanSettingsError.missingCookie()\n        #endif\n    }\n}\n\nstruct AlibabaCodingPlanAPIFetchStrategy: ProviderFetchStrategy {\n    let id: String = \"alibaba-coding-plan.api\"\n    let kind: ProviderFetchKind = .apiToken\n\n    func isAvailable(_ context: ProviderFetchContext) async -> Bool {\n        Self.resolveToken(environment: context.env) != nil\n    }\n\n    func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {\n        guard let apiKey = Self.resolveToken(environment: context.env) else {\n            throw AlibabaCodingPlanSettingsError.missingToken\n        }\n        let region = context.settings?.alibaba?.apiRegion ?? .international\n        let usage = try await AlibabaCodingPlanUsageFetcher.fetchUsage(\n            apiKey: apiKey,\n            region: region,\n            environment: context.env)\n        return self.makeResult(\n            usage: usage.toUsageSnapshot(),\n            sourceLabel: \"api\")\n    }\n\n    func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool {\n        false\n    }\n\n    private static func resolveToken(environment: [String: String]) -> String? {\n        ProviderTokenResolver.alibabaToken(environment: environment)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Alibaba/AlibabaCodingPlanSettingsReader.swift",
    "content": "import Foundation\n\npublic struct AlibabaCodingPlanSettingsReader: Sendable {\n    public static let apiTokenKey = \"ALIBABA_CODING_PLAN_API_KEY\"\n    public static let cookieHeaderKey = \"ALIBABA_CODING_PLAN_COOKIE\"\n    public static let hostKey = \"ALIBABA_CODING_PLAN_HOST\"\n    public static let quotaURLKey = \"ALIBABA_CODING_PLAN_QUOTA_URL\"\n\n    public static func apiToken(\n        environment: [String: String] = ProcessInfo.processInfo.environment) -> String?\n    {\n        self.cleaned(environment[self.apiTokenKey])\n    }\n\n    public static func hostOverride(\n        environment: [String: String] = ProcessInfo.processInfo.environment) -> String?\n    {\n        self.cleaned(environment[self.hostKey])\n    }\n\n    public static func cookieHeader(\n        environment: [String: String] = ProcessInfo.processInfo.environment) -> String?\n    {\n        self.cleaned(environment[self.cookieHeaderKey])\n    }\n\n    public static func quotaURL(\n        environment: [String: String] = ProcessInfo.processInfo.environment) -> URL?\n    {\n        guard let raw = self.cleaned(environment[self.quotaURLKey]) else { return nil }\n        if let url = URL(string: raw), url.scheme != nil {\n            return url\n        }\n        return URL(string: \"https://\\(raw)\")\n    }\n\n    static func cleaned(_ raw: String?) -> String? {\n        guard var value = raw?.trimmingCharacters(in: .whitespacesAndNewlines), !value.isEmpty else {\n            return nil\n        }\n\n        if (value.hasPrefix(\"\\\"\") && value.hasSuffix(\"\\\"\")) ||\n            (value.hasPrefix(\"'\") && value.hasSuffix(\"'\"))\n        {\n            value.removeFirst()\n            value.removeLast()\n        }\n\n        value = value.trimmingCharacters(in: .whitespacesAndNewlines)\n        return value.isEmpty ? nil : value\n    }\n}\n\npublic enum AlibabaCodingPlanSettingsError: LocalizedError, Sendable {\n    case missingToken\n    case missingCookie(details: String? = nil)\n    case invalidCookie\n\n    public var errorDescription: String? {\n        switch self {\n        case .missingToken:\n            return \"Alibaba Coding Plan API key not found. \" +\n                \"Set apiKey in ~/.codexbar/config.json or ALIBABA_CODING_PLAN_API_KEY.\"\n        case let .missingCookie(details):\n            let base = \"No Alibaba Coding Plan session cookies found in browsers. \" +\n                \"If you use Safari, enable Full Disk Access for CodexBar/Terminal or paste a manual Cookie header.\"\n            guard let details, !details.isEmpty else { return base }\n            return \"\\(base) \\(details)\"\n        case .invalidCookie:\n            return \"Alibaba Coding Plan cookie header is invalid.\"\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Alibaba/AlibabaCodingPlanUsageFetcher.swift",
    "content": "import Foundation\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n\n// swiftlint:disable type_body_length\npublic struct AlibabaCodingPlanUsageFetcher: Sendable {\n    private static let log = CodexBarLog.logger(\"alibaba-coding-plan\")\n    private static let browserLikeUserAgent =\n        \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) \" +\n        \"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36\"\n    private static let safariLikeUserAgent =\n        \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) \" +\n        \"AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.3 Safari/605.1.15\"\n\n    enum AuthMode: Sendable {\n        case apiKey\n        case webSession\n    }\n\n    public static func fetchUsage(\n        apiKey: String,\n        region: AlibabaCodingPlanAPIRegion = .international,\n        environment: [String: String] = ProcessInfo.processInfo.environment,\n        now: Date = Date()) async throws -> AlibabaCodingPlanUsageSnapshot\n    {\n        let cleanedKey = apiKey.trimmingCharacters(in: .whitespacesAndNewlines)\n        guard !cleanedKey.isEmpty else {\n            throw AlibabaCodingPlanUsageError.invalidCredentials\n        }\n\n        if region != .international {\n            return try await self.fetchUsageOnce(\n                apiKey: cleanedKey,\n                region: region,\n                environment: environment,\n                now: now)\n        }\n\n        do {\n            return try await self.fetchUsageOnce(\n                apiKey: cleanedKey,\n                region: .international,\n                environment: environment,\n                now: now)\n        } catch let error as AlibabaCodingPlanUsageError {\n            guard error.shouldRetryOnAlternateRegion else { throw error }\n            Self.log.debug(\"Alibaba Coding Plan request failed on intl host; retrying cn host\")\n            return try await self.fetchUsageOnce(\n                apiKey: cleanedKey,\n                region: .chinaMainland,\n                environment: environment,\n                now: now)\n        }\n    }\n\n    public static func fetchUsage(\n        cookieHeader: String,\n        region: AlibabaCodingPlanAPIRegion = .international,\n        environment: [String: String] = ProcessInfo.processInfo.environment,\n        now: Date = Date()) async throws -> AlibabaCodingPlanUsageSnapshot\n    {\n        guard let normalizedCookie = CookieHeaderNormalizer.normalize(cookieHeader) else {\n            throw AlibabaCodingPlanSettingsError.invalidCookie\n        }\n\n        if region != .international {\n            return try await self.fetchUsageOnce(\n                cookieHeader: normalizedCookie,\n                region: region,\n                environment: environment,\n                now: now)\n        }\n\n        do {\n            return try await self.fetchUsageOnce(\n                cookieHeader: normalizedCookie,\n                region: .international,\n                environment: environment,\n                now: now)\n        } catch let error as AlibabaCodingPlanUsageError {\n            guard error.shouldRetryOnAlternateRegion else { throw error }\n            Self.log.debug(\"Alibaba Coding Plan cookie request failed on intl host; retrying cn host\")\n            return try await self.fetchUsageOnce(\n                cookieHeader: normalizedCookie,\n                region: .chinaMainland,\n                environment: environment,\n                now: now)\n        }\n    }\n\n    private static func fetchUsageOnce(\n        apiKey: String,\n        region: AlibabaCodingPlanAPIRegion,\n        environment: [String: String],\n        now: Date) async throws -> AlibabaCodingPlanUsageSnapshot\n    {\n        let url = self.resolveQuotaURL(region: region, environment: environment)\n        var request = URLRequest(url: url)\n        request.httpMethod = \"POST\"\n        request.httpBody = self.queryCodingPlanAPIRequestBody(region: region)\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Content-Type\")\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Accept\")\n        request.setValue(\"Bearer \\(apiKey)\", forHTTPHeaderField: \"Authorization\")\n        request.setValue(apiKey, forHTTPHeaderField: \"x-api-key\")\n        request.setValue(apiKey, forHTTPHeaderField: \"X-DashScope-API-Key\")\n        request.setValue(Self.browserLikeUserAgent, forHTTPHeaderField: \"User-Agent\")\n        request.setValue(region.gatewayBaseURLString, forHTTPHeaderField: \"Origin\")\n        request.setValue(region.dashboardURL.absoluteString, forHTTPHeaderField: \"Referer\")\n\n        let (data, response) = try await URLSession.shared.data(for: request)\n        guard let httpResponse = response as? HTTPURLResponse else {\n            throw AlibabaCodingPlanUsageError.networkError(\"Invalid response\")\n        }\n\n        guard httpResponse.statusCode == 200 else {\n            if httpResponse.statusCode == 401 || httpResponse.statusCode == 403 {\n                throw AlibabaCodingPlanUsageError.invalidCredentials\n            }\n            let body = String(data: data, encoding: .utf8) ?? \"\"\n            Self.log.error(\"Alibaba Coding Plan returned \\(httpResponse.statusCode): \\(body)\")\n            throw AlibabaCodingPlanUsageError.apiError(\"HTTP \\(httpResponse.statusCode)\")\n        }\n\n        return try self.parseUsageSnapshot(from: data, now: now, authMode: .apiKey)\n    }\n\n    private static func fetchUsageOnce(\n        cookieHeader: String,\n        region: AlibabaCodingPlanAPIRegion,\n        environment: [String: String],\n        now: Date) async throws -> AlibabaCodingPlanUsageSnapshot\n    {\n        let url = self.resolveConsoleQuotaURL(region: region, environment: environment)\n        let secToken = try await self.resolveConsoleSECToken(\n            cookieHeader: cookieHeader,\n            region: region,\n            environment: environment)\n        let anonymousID = self.extractCookieValue(name: \"cna\", from: cookieHeader)\n\n        var request = URLRequest(url: url)\n        request.httpMethod = \"POST\"\n        request.httpBody = self.queryCodingPlanConsoleRequestBody(\n            region: region,\n            secToken: secToken,\n            anonymousID: anonymousID)\n        request.setValue(\"application/x-www-form-urlencoded\", forHTTPHeaderField: \"Content-Type\")\n        request.setValue(\"*/*\", forHTTPHeaderField: \"Accept\")\n        request.setValue(cookieHeader, forHTTPHeaderField: \"Cookie\")\n        if let csrf = self.extractCookieValue(name: \"login_aliyunid_csrf\", from: cookieHeader) ??\n            self.extractCookieValue(name: \"csrf\", from: cookieHeader)\n        {\n            request.setValue(csrf, forHTTPHeaderField: \"x-xsrf-token\")\n            request.setValue(csrf, forHTTPHeaderField: \"x-csrf-token\")\n        }\n        request.setValue(\"XMLHttpRequest\", forHTTPHeaderField: \"X-Requested-With\")\n        request.setValue(Self.browserLikeUserAgent, forHTTPHeaderField: \"User-Agent\")\n        request.setValue(region.gatewayBaseURLString, forHTTPHeaderField: \"Origin\")\n        request.setValue(region.consoleRefererURL.absoluteString, forHTTPHeaderField: \"Referer\")\n\n        let (data, response) = try await URLSession.shared.data(for: request)\n        guard let httpResponse = response as? HTTPURLResponse else {\n            throw AlibabaCodingPlanUsageError.networkError(\"Invalid response\")\n        }\n\n        guard httpResponse.statusCode == 200 else {\n            if httpResponse.statusCode == 401 || httpResponse.statusCode == 403 {\n                throw AlibabaCodingPlanUsageError.loginRequired\n            }\n            let body = String(data: data, encoding: .utf8) ?? \"\"\n            Self.log.error(\"Alibaba Coding Plan returned \\(httpResponse.statusCode): \\(body)\")\n            throw AlibabaCodingPlanUsageError.apiError(\"HTTP \\(httpResponse.statusCode)\")\n        }\n\n        return try self.parseUsageSnapshot(from: data, now: now, authMode: .webSession)\n    }\n\n    private static func queryCodingPlanAPIRequestBody(region: AlibabaCodingPlanAPIRegion) -> Data {\n        let payload: [String: Any] = [\n            \"queryCodingPlanInstanceInfoRequest\": [\n                \"commodityCode\": region.commodityCode,\n            ],\n        ]\n        return (try? JSONSerialization.data(withJSONObject: payload, options: [])) ?? Data(\"{}\".utf8)\n    }\n\n    private static func queryCodingPlanConsoleRequestBody(\n        region: AlibabaCodingPlanAPIRegion,\n        secToken: String,\n        anonymousID: String?) -> Data\n    {\n        let traceID = UUID().uuidString.lowercased()\n        var cornerstoneParam: [String: Any] = [\n            \"feTraceId\": traceID,\n            \"feURL\": region.dashboardURL.absoluteString,\n            \"protocol\": \"V2\",\n            \"console\": \"ONE_CONSOLE\",\n            \"productCode\": \"p_efm\",\n            \"domain\": region.consoleDomain,\n            \"consoleSite\": region.consoleSite,\n            \"userNickName\": \"\",\n            \"userPrincipalName\": \"\",\n            \"xsp_lang\": \"en-US\",\n        ]\n        if let anonymousID, !anonymousID.isEmpty {\n            cornerstoneParam[\"X-Anonymous-Id\"] = anonymousID\n        }\n\n        let paramsObject: [String: Any] = [\n            \"Api\": region.consoleQuotaAPIName,\n            \"V\": \"1.0\",\n            \"Data\": [\n                \"queryCodingPlanInstanceInfoRequest\": [\n                    \"commodityCode\": region.commodityCode,\n                    \"onlyLatestOne\": true,\n                ],\n                \"cornerstoneParam\": cornerstoneParam,\n            ],\n        ]\n\n        guard let paramsData = try? JSONSerialization.data(withJSONObject: paramsObject, options: []),\n              let paramsString = String(data: paramsData, encoding: .utf8)\n        else {\n            return Data()\n        }\n\n        var components = URLComponents()\n        components.queryItems = [\n            URLQueryItem(name: \"params\", value: paramsString),\n            URLQueryItem(name: \"region\", value: region.currentRegionID),\n            URLQueryItem(name: \"sec_token\", value: secToken),\n        ]\n        return Data((components.percentEncodedQuery ?? \"\").utf8)\n    }\n\n    static func resolveConsoleQuotaURL(\n        region: AlibabaCodingPlanAPIRegion,\n        environment: [String: String]) -> URL\n    {\n        if let override = AlibabaCodingPlanSettingsReader.quotaURL(environment: environment) {\n            return override\n        }\n        if let host = AlibabaCodingPlanSettingsReader.hostOverride(environment: environment),\n           let hostURL = self.consoleURL(from: host, region: region)\n        {\n            return hostURL\n        }\n        return region.consoleRPCURL\n    }\n\n    static func resolveQuotaURL(\n        region: AlibabaCodingPlanAPIRegion,\n        environment: [String: String]) -> URL\n    {\n        if let override = AlibabaCodingPlanSettingsReader.quotaURL(environment: environment) {\n            return override\n        }\n        if let host = AlibabaCodingPlanSettingsReader.hostOverride(environment: environment),\n           let hostURL = self.url(from: host, region: region)\n        {\n            return hostURL\n        }\n        return region.quotaURL\n    }\n\n    static func resolveConsoleDashboardURL(\n        region: AlibabaCodingPlanAPIRegion,\n        environment: [String: String]) -> URL\n    {\n        if let override = AlibabaCodingPlanSettingsReader.hostOverride(environment: environment),\n           let hostURL = self.dashboardURL(from: override, region: region)\n        {\n            return hostURL\n        }\n        return region.dashboardURL\n    }\n\n    static func url(from rawHost: String, region: AlibabaCodingPlanAPIRegion) -> URL? {\n        let cleaned = AlibabaCodingPlanSettingsReader.cleaned(rawHost)\n        guard let cleaned else { return nil }\n\n        let base: URL? = if let url = URL(string: cleaned), url.scheme != nil {\n            url\n        } else {\n            URL(string: \"https://\\(cleaned)\")\n        }\n        guard let base else { return nil }\n\n        var components = URLComponents(url: base, resolvingAgainstBaseURL: false)\n        components?.path = \"/data/api.json\"\n        components?.queryItems = [\n            URLQueryItem(\n                name: \"action\",\n                value: \"zeldaEasy.broadscope-bailian.codingPlan.queryCodingPlanInstanceInfoV2\"),\n            URLQueryItem(name: \"product\", value: \"broadscope-bailian\"),\n            URLQueryItem(name: \"api\", value: \"queryCodingPlanInstanceInfoV2\"),\n            URLQueryItem(name: \"currentRegionId\", value: region.currentRegionID),\n        ]\n        return components?.url\n    }\n\n    static func consoleURL(from rawHost: String, region: AlibabaCodingPlanAPIRegion) -> URL? {\n        let cleaned = AlibabaCodingPlanSettingsReader.cleaned(rawHost)\n        guard let cleaned else { return nil }\n\n        let base: URL? = if let url = URL(string: cleaned), url.scheme != nil {\n            url\n        } else {\n            URL(string: \"https://\\(cleaned)\")\n        }\n        guard let base else { return nil }\n\n        var components = URLComponents(url: base, resolvingAgainstBaseURL: false)\n        components?.path = \"/data/api.json\"\n        components?.queryItems = [\n            URLQueryItem(name: \"action\", value: region.consoleRPCAction),\n            URLQueryItem(name: \"product\", value: region.consoleRPCProduct),\n            URLQueryItem(name: \"api\", value: region.consoleQuotaAPIName),\n            URLQueryItem(name: \"_v\", value: \"undefined\"),\n        ]\n        return components?.url\n    }\n\n    private static func resolveConsoleSECToken(\n        cookieHeader: String,\n        region: AlibabaCodingPlanAPIRegion,\n        environment: [String: String]) async throws -> String\n    {\n        let cookieSECToken = self.extractCookieValue(name: \"sec_token\", from: cookieHeader)\n\n        let dashboardURL = self.resolveConsoleDashboardURL(region: region, environment: environment)\n\n        var request = URLRequest(url: dashboardURL)\n        request.httpMethod = \"GET\"\n        request.setValue(cookieHeader, forHTTPHeaderField: \"Cookie\")\n        request.setValue(Self.safariLikeUserAgent, forHTTPHeaderField: \"User-Agent\")\n        request.setValue(\n            \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\",\n            forHTTPHeaderField: \"Accept\")\n\n        if let (data, response) = try? await URLSession.shared.data(for: request),\n           let httpResponse = response as? HTTPURLResponse,\n           httpResponse.statusCode == 200,\n           let html = String(data: data, encoding: .utf8),\n           let token = self.extractConsoleSECToken(from: html),\n           !token.isEmpty\n        {\n            return token\n        }\n\n        if let token = try? await self.fetchSECTokenFromUserInfo(\n            cookieHeader: cookieHeader,\n            region: region,\n            environment: environment)\n        {\n            return token\n        }\n\n        if let cookieSECToken, !cookieSECToken.isEmpty {\n            return cookieSECToken\n        }\n\n        throw AlibabaCodingPlanUsageError.loginRequired\n    }\n\n    static func dashboardURL(from rawHost: String, region: AlibabaCodingPlanAPIRegion) -> URL? {\n        let cleaned = AlibabaCodingPlanSettingsReader.cleaned(rawHost)\n        guard let cleaned else { return nil }\n\n        let base: URL? = if let url = URL(string: cleaned), url.scheme != nil {\n            url\n        } else {\n            URL(string: \"https://\\(cleaned)\")\n        }\n        guard let base else { return nil }\n\n        guard var components = URLComponents(url: base, resolvingAgainstBaseURL: false),\n              let dashboardComponents = URLComponents(\n                  url: region.dashboardURL,\n                  resolvingAgainstBaseURL: false)\n        else {\n            return nil\n        }\n\n        components.path = dashboardComponents.path\n        components.percentEncodedQuery = dashboardComponents.percentEncodedQuery\n        components.fragment = dashboardComponents.fragment\n        return components.url\n    }\n\n    private static func fetchSECTokenFromUserInfo(\n        cookieHeader: String,\n        region: AlibabaCodingPlanAPIRegion,\n        environment: [String: String]) async throws -> String?\n    {\n        let gatewayBaseURL = self.resolveConsoleGatewayBaseURL(region: region, environment: environment)\n        let userInfoURL = gatewayBaseURL.appendingPathComponent(\"tool/user/info.json\")\n        var request = URLRequest(url: userInfoURL)\n        request.httpMethod = \"GET\"\n        request.setValue(cookieHeader, forHTTPHeaderField: \"Cookie\")\n        request.setValue(Self.safariLikeUserAgent, forHTTPHeaderField: \"User-Agent\")\n        request.setValue(\"application/json, text/plain, */*\", forHTTPHeaderField: \"Accept\")\n        let referer = gatewayBaseURL.absoluteString.hasSuffix(\"/\") ? gatewayBaseURL.absoluteString : gatewayBaseURL\n            .absoluteString + \"/\"\n        request.setValue(referer, forHTTPHeaderField: \"Referer\")\n\n        let (data, response) = try await URLSession.shared.data(for: request)\n        guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {\n            return nil\n        }\n\n        let object = try JSONSerialization.jsonObject(with: data, options: [])\n        let expanded = self.expandedJSON(object)\n        return self.findFirstString(forKeys: [\"secToken\", \"sec_token\"], in: expanded)\n    }\n\n    private static func resolveConsoleGatewayBaseURL(\n        region: AlibabaCodingPlanAPIRegion,\n        environment: [String: String]) -> URL\n    {\n        if let host = AlibabaCodingPlanSettingsReader.hostOverride(environment: environment),\n           let hostURL = self.baseURL(from: host)\n        {\n            return hostURL\n        }\n        return URL(string: region.gatewayBaseURLString)!\n    }\n\n    private static func baseURL(from rawHost: String) -> URL? {\n        let cleaned = AlibabaCodingPlanSettingsReader.cleaned(rawHost)\n        guard let cleaned else { return nil }\n\n        let base: URL? = if let url = URL(string: cleaned), url.scheme != nil {\n            url\n        } else {\n            URL(string: \"https://\\(cleaned)\")\n        }\n        guard let base else { return nil }\n\n        guard var components = URLComponents(url: base, resolvingAgainstBaseURL: false) else {\n            return nil\n        }\n        components.path = \"\"\n        components.query = nil\n        components.fragment = nil\n        return components.url\n    }\n\n    static func parseUsageSnapshot(\n        from data: Data,\n        now: Date = Date(),\n        authMode: AuthMode = .webSession) throws -> AlibabaCodingPlanUsageSnapshot\n    {\n        guard !data.isEmpty else {\n            throw AlibabaCodingPlanUsageError.parseFailed(\"Empty response body\")\n        }\n\n        let object = try JSONSerialization.jsonObject(with: data, options: [])\n        let expanded = self.expandedJSON(object)\n        guard let dictionary = expanded as? [String: Any] else {\n            throw AlibabaCodingPlanUsageError.parseFailed(\"Unexpected payload\")\n        }\n\n        if let statusCode = self.findFirstInt(forKeys: [\"statusCode\", \"status_code\", \"code\"], in: dictionary),\n           statusCode != 0,\n           statusCode != 200\n        {\n            let message = self.findFirstString(\n                forKeys: [\"statusMessage\", \"status_msg\", \"message\", \"msg\"],\n                in: dictionary)\n                ?? \"status code \\(statusCode)\"\n            let lower = message.lowercased()\n            if statusCode == 401 || statusCode == 403 || lower.contains(\"api key\") || lower.contains(\"unauthorized\") {\n                throw AlibabaCodingPlanUsageError.invalidCredentials\n            }\n            throw AlibabaCodingPlanUsageError.apiError(message)\n        }\n\n        if let codeText = self.findFirstString(forKeys: [\"code\", \"status\", \"statusCode\"], in: dictionary) {\n            let normalizedCode = codeText.lowercased()\n            if normalizedCode.contains(\"needlogin\") || normalizedCode.contains(\"login\") {\n                if authMode == .apiKey {\n                    throw AlibabaCodingPlanUsageError.apiError(\n                        \"This Alibaba endpoint requires a console session for this account/region. \" +\n                            \"API key mode may be unavailable in CN on this endpoint.\")\n                }\n                throw AlibabaCodingPlanUsageError.loginRequired\n            }\n        }\n        if let messageText = self.findFirstString(forKeys: [\"message\", \"msg\", \"statusMessage\"], in: dictionary) {\n            let normalizedMessage = messageText.lowercased()\n            if normalizedMessage.contains(\"log in\") || normalizedMessage.contains(\"login\") {\n                if authMode == .apiKey {\n                    throw AlibabaCodingPlanUsageError.apiError(\n                        \"This Alibaba endpoint requires a console session for this account/region. \" +\n                            \"API key mode may be unavailable in CN on this endpoint.\")\n                }\n                throw AlibabaCodingPlanUsageError.loginRequired\n            }\n        }\n\n        let instanceInfo = self.findActiveInstanceInfo(in: dictionary, now: now)\n        let instanceCount = self.findFirstArray(\n            forKeys: [\"codingPlanInstanceInfos\", \"coding_plan_instance_infos\"],\n            in: dictionary)?\n            .compactMap { $0 as? [String: Any] }\n            .count ?? 0\n        let shouldScopeQuotaToSelectedInstance =\n            instanceCount > 1 &&\n            (instanceInfo.map { self.activeSignalScore(in: $0, now: now) > 0 } ?? false)\n        let quota = if shouldScopeQuotaToSelectedInstance, let instanceInfo {\n            self.findQuotaInfo(in: instanceInfo)\n        } else {\n            self.findQuotaInfo(in: instanceInfo ?? [:]) ?? self.findQuotaInfo(in: dictionary)\n        }\n        guard let quota else {\n            if let fallback = self.parsePlanVisibleActiveFallback(\n                payload: dictionary,\n                instanceInfo: instanceInfo,\n                now: now)\n            {\n                return fallback\n            }\n            let diagnostics = self.payloadDiagnostics(payload: dictionary)\n            Self.log.error(\"Alibaba coding plan quota payload missing expected fields: \\(diagnostics)\")\n            throw AlibabaCodingPlanUsageError.parseFailed(\"Missing coding plan quota data (\\(diagnostics))\")\n        }\n\n        let planName = self.findPlanName(in: instanceInfo ?? [:]) ?? self.findPlanName(in: dictionary)\n\n        let snapshot = AlibabaCodingPlanUsageSnapshot(\n            planName: planName,\n            fiveHourUsedQuota: self.anyInt(for: [\"per5HourUsedQuota\", \"perFiveHourUsedQuota\"], in: quota),\n            fiveHourTotalQuota: self.anyInt(for: [\"per5HourTotalQuota\", \"perFiveHourTotalQuota\"], in: quota),\n            fiveHourNextRefreshTime: self.anyDate(\n                for: [\"per5HourQuotaNextRefreshTime\", \"perFiveHourQuotaNextRefreshTime\"],\n                in: quota),\n            weeklyUsedQuota: self.anyInt(for: [\"perWeekUsedQuota\"], in: quota),\n            weeklyTotalQuota: self.anyInt(for: [\"perWeekTotalQuota\"], in: quota),\n            weeklyNextRefreshTime: self.anyDate(for: [\"perWeekQuotaNextRefreshTime\"], in: quota),\n            monthlyUsedQuota: self.anyInt(for: [\"perBillMonthUsedQuota\", \"perMonthUsedQuota\"], in: quota),\n            monthlyTotalQuota: self.anyInt(for: [\"perBillMonthTotalQuota\", \"perMonthTotalQuota\"], in: quota),\n            monthlyNextRefreshTime: self.anyDate(\n                for: [\"perBillMonthQuotaNextRefreshTime\", \"perMonthQuotaNextRefreshTime\"],\n                in: quota),\n            updatedAt: now)\n\n        if snapshot.fiveHourTotalQuota == nil,\n           snapshot.weeklyTotalQuota == nil,\n           snapshot.monthlyTotalQuota == nil\n        {\n            if let fallback = self.parsePlanVisibleActiveFallback(\n                payload: dictionary,\n                instanceInfo: instanceInfo,\n                now: now)\n            {\n                return fallback\n            }\n            let diagnostics = self.payloadDiagnostics(payload: dictionary)\n            Self.log.error(\"Alibaba coding plan payload had no usable windows: \\(diagnostics)\")\n            throw AlibabaCodingPlanUsageError.parseFailed(\"No quota windows found in payload (\\(diagnostics))\")\n        }\n\n        return snapshot\n    }\n\n    private static func findPlanName(in payload: [String: Any]) -> String? {\n        if let infos = self.findFirstArray(\n            forKeys: [\"codingPlanInstanceInfos\", \"coding_plan_instance_infos\"],\n            in: payload)\n        {\n            for item in infos {\n                guard let info = item as? [String: Any] else { continue }\n                let candidates = [\n                    self.anyString(for: [\"planName\", \"plan_name\"], in: info),\n                    self.anyString(for: [\"instanceName\", \"instance_name\"], in: info),\n                    self.anyString(for: [\"packageName\", \"package_name\"], in: info),\n                ]\n                for candidate in candidates {\n                    if let candidate, !candidate.isEmpty {\n                        return candidate\n                    }\n                }\n            }\n        }\n        return self.findFirstString(forKeys: [\"planName\", \"plan_name\", \"packageName\", \"package_name\"], in: payload)\n    }\n\n    private static func findActiveInstanceInfo(in payload: [String: Any], now: Date = Date()) -> [String: Any]? {\n        guard let infos = self.findFirstArray(\n            forKeys: [\"codingPlanInstanceInfos\", \"coding_plan_instance_infos\"],\n            in: payload)\n        else {\n            return nil\n        }\n\n        var first: [String: Any]?\n        var best: [String: Any]?\n        var bestScore = Int.min\n        for item in infos {\n            guard let info = item as? [String: Any] else { continue }\n            first = first ?? info\n            let score = self.activeSignalScore(in: info, now: now)\n            if score > bestScore {\n                best = info\n                bestScore = score\n            }\n        }\n        if bestScore > 0 {\n            return best\n        }\n        return first\n    }\n\n    private static func parsePlanVisibleActiveFallback(\n        payload: [String: Any],\n        instanceInfo: [String: Any]?,\n        now: Date) -> AlibabaCodingPlanUsageSnapshot?\n    {\n        let source = instanceInfo ?? payload\n        guard self.hasPositiveActivePlanSignal(in: source, payload: payload, now: now),\n              let planName = self.findPlanName(in: source) ?? self.findPlanName(in: payload)\n        else {\n            return nil\n        }\n\n        return AlibabaCodingPlanUsageSnapshot(\n            planName: planName,\n            fiveHourUsedQuota: nil,\n            fiveHourTotalQuota: nil,\n            fiveHourNextRefreshTime: nil,\n            weeklyUsedQuota: nil,\n            weeklyTotalQuota: nil,\n            weeklyNextRefreshTime: nil,\n            monthlyUsedQuota: nil,\n            monthlyTotalQuota: nil,\n            monthlyNextRefreshTime: nil,\n            updatedAt: now)\n    }\n\n    private static func hasPositiveActivePlanSignal(\n        in source: [String: Any],\n        payload: [String: Any],\n        now: Date) -> Bool\n    {\n        if self.containsPlanInstances(in: payload) {\n            return self.activeSignalScore(in: source, now: now) > 0\n        }\n        return self.activeSignalScore(in: source, now: now) > 0 || self.activeSignalScore(in: payload, now: now) > 0\n    }\n\n    private static func containsPlanInstances(in payload: [String: Any]) -> Bool {\n        guard let infos = self.findFirstArray(\n            forKeys: [\"codingPlanInstanceInfos\", \"coding_plan_instance_infos\"],\n            in: payload)\n        else {\n            return false\n        }\n        return infos.contains { $0 is [String: Any] }\n    }\n\n    private static func activeSignalScore(in source: [String: Any], now: Date) -> Int {\n        if let status = self.anyString(for: [\"status\", \"instanceStatus\"], in: source)?.uppercased() {\n            if [\"VALID\", \"ACTIVE\"].contains(status) {\n                return 3\n            }\n            if [\"EXPIRED\", \"INVALID\", \"INACTIVE\", \"DISABLED\", \"TERMINATED\", \"STOPPED\"].contains(status) {\n                return -1\n            }\n        }\n\n        if let isActive = self.anyBool(for: [\"isActive\", \"active\"], in: source) {\n            return isActive ? 3 : -1\n        }\n\n        if let expiry = self.anyDate(for: [\"endTime\", \"periodEndTime\", \"expireTime\", \"expirationTime\"], in: source),\n           expiry.timeIntervalSince(now) > 0\n        {\n            return 1\n        }\n\n        return 0\n    }\n\n    private static func payloadDiagnostics(payload: [String: Any]) -> String {\n        let topKeys = payload.keys.sorted()\n        let dataDict = self.findFirstDictionary(forKeys: [\"data\", \"successResponse\", \"success_response\"], in: payload)\n        let dataKeys = dataDict?.keys.sorted() ?? []\n        let instanceInfo = self.findActiveInstanceInfo(in: payload)\n        let instanceKeys = instanceInfo?.keys.sorted() ?? []\n        let hasQuota = self.findQuotaInfo(in: payload) != nil\n        let planUsage =\n            self.anyString(for: [\"planUsage\", \"usageRate\", \"usage\", \"usageValue\"], in: instanceInfo ?? [:]) ??\n            self.findFirstString(forKeys: [\"planUsage\", \"usageRate\", \"usage\", \"usageValue\"], in: payload)\n        let compactPlanUsage = planUsage?.replacingOccurrences(of: \"\\n\", with: \" \") ?? \"none\"\n        let status = self.anyString(for: [\"status\", \"instanceStatus\"], in: instanceInfo ?? [:]) ?? \"none\"\n\n        return \"topKeys=\\(topKeys.joined(separator: \",\")) dataKeys=\\(dataKeys.joined(separator: \",\")) \" +\n            \"instanceKeys=\\(instanceKeys.joined(separator: \",\")) hasQuota=\\(hasQuota ? \"1\" : \"0\") \" +\n            \"status=\\(status) planUsage=\\(compactPlanUsage)\"\n    }\n\n    private static func findQuotaInfo(in payload: [String: Any]) -> [String: Any]? {\n        if let direct = self.findFirstDictionary(\n            forKeys: [\"codingPlanQuotaInfo\", \"coding_plan_quota_info\"],\n            in: payload)\n        {\n            return direct\n        }\n        return self.findFirstDictionary(\n            matchingAnyKey: [\n                \"per5HourUsedQuota\",\n                \"per5HourTotalQuota\",\n                \"perWeekUsedQuota\",\n                \"perWeekTotalQuota\",\n                \"perBillMonthUsedQuota\",\n                \"perBillMonthTotalQuota\",\n            ],\n            in: payload)\n    }\n\n    private static func findFirstDictionary(forKeys keys: [String], in value: Any) -> [String: Any]? {\n        guard let dict = value as? [String: Any] else { return nil }\n        for key in keys {\n            if let nested = dict[key] as? [String: Any] {\n                return nested\n            }\n        }\n        for nestedValue in dict.values {\n            if let nested = self.findFirstDictionary(forKeys: keys, in: nestedValue) {\n                return nested\n            }\n        }\n        if let array = value as? [Any] {\n            for item in array {\n                if let nested = self.findFirstDictionary(forKeys: keys, in: item) {\n                    return nested\n                }\n            }\n        }\n        return nil\n    }\n\n    private static func findFirstDictionary(matchingAnyKey keys: [String], in value: Any) -> [String: Any]? {\n        if let dict = value as? [String: Any] {\n            if keys.contains(where: { dict[$0] != nil }) {\n                return dict\n            }\n            for nestedValue in dict.values {\n                if let nested = self.findFirstDictionary(matchingAnyKey: keys, in: nestedValue) {\n                    return nested\n                }\n            }\n            return nil\n        }\n        if let array = value as? [Any] {\n            for item in array {\n                if let nested = self.findFirstDictionary(matchingAnyKey: keys, in: item) {\n                    return nested\n                }\n            }\n        }\n        return nil\n    }\n\n    private static func findFirstArray(forKeys keys: [String], in value: Any) -> [Any]? {\n        guard let dict = value as? [String: Any] else {\n            if let array = value as? [Any] {\n                for item in array {\n                    if let found = self.findFirstArray(forKeys: keys, in: item) {\n                        return found\n                    }\n                }\n            }\n            return nil\n        }\n        for key in keys {\n            if let array = dict[key] as? [Any] {\n                return array\n            }\n        }\n        for nested in dict.values {\n            if let found = self.findFirstArray(forKeys: keys, in: nested) {\n                return found\n            }\n        }\n        return nil\n    }\n\n    private static func findFirstInt(forKeys keys: [String], in value: Any) -> Int? {\n        if let dict = value as? [String: Any] {\n            for key in keys {\n                if let parsed = self.parseInt(dict[key]) {\n                    return parsed\n                }\n            }\n            for nested in dict.values {\n                if let parsed = self.findFirstInt(forKeys: keys, in: nested) {\n                    return parsed\n                }\n            }\n            return nil\n        }\n        if let array = value as? [Any] {\n            for item in array {\n                if let parsed = self.findFirstInt(forKeys: keys, in: item) {\n                    return parsed\n                }\n            }\n        }\n        return nil\n    }\n\n    private static func findFirstString(forKeys keys: [String], in value: Any) -> String? {\n        if let dict = value as? [String: Any] {\n            for key in keys {\n                if let parsed = self.parseString(dict[key]) {\n                    return parsed\n                }\n            }\n            for nested in dict.values {\n                if let parsed = self.findFirstString(forKeys: keys, in: nested) {\n                    return parsed\n                }\n            }\n            return nil\n        }\n        if let array = value as? [Any] {\n            for item in array {\n                if let parsed = self.findFirstString(forKeys: keys, in: item) {\n                    return parsed\n                }\n            }\n        }\n        return nil\n    }\n\n    private static func findFirstDate(forKeys keys: [String], in value: Any) -> Date? {\n        if let dict = value as? [String: Any] {\n            for key in keys {\n                if let parsed = self.parseDate(dict[key]) {\n                    return parsed\n                }\n            }\n            for nested in dict.values {\n                if let parsed = self.findFirstDate(forKeys: keys, in: nested) {\n                    return parsed\n                }\n            }\n            return nil\n        }\n        if let array = value as? [Any] {\n            for item in array {\n                if let parsed = self.findFirstDate(forKeys: keys, in: item) {\n                    return parsed\n                }\n            }\n        }\n        return nil\n    }\n\n    private static func expandedJSON(_ value: Any) -> Any {\n        if let dict = value as? [String: Any] {\n            var expanded: [String: Any] = [:]\n            expanded.reserveCapacity(dict.count)\n            for (key, nested) in dict {\n                expanded[key] = self.expandedJSON(nested)\n            }\n            return expanded\n        }\n        if let array = value as? [Any] {\n            return array.map { self.expandedJSON($0) }\n        }\n        if let string = value as? String,\n           let data = string.data(using: .utf8),\n           let nested = try? JSONSerialization.jsonObject(with: data, options: []),\n           nested is [String: Any] || nested is [Any]\n        {\n            return self.expandedJSON(nested)\n        }\n        return value\n    }\n\n    private static func anyInt(for keys: [String], in dict: [String: Any]) -> Int? {\n        for key in keys {\n            if let value = self.parseInt(dict[key]) {\n                return value\n            }\n        }\n        return nil\n    }\n\n    private static func anyString(for keys: [String], in dict: [String: Any]) -> String? {\n        for key in keys {\n            if let value = self.parseString(dict[key]) {\n                return value\n            }\n        }\n        return nil\n    }\n\n    private static func anyDate(for keys: [String], in dict: [String: Any]) -> Date? {\n        for key in keys {\n            if let value = self.parseDate(dict[key]) {\n                return value\n            }\n        }\n        return nil\n    }\n\n    private static func anyPercent(for keys: [String], in dict: [String: Any]) -> Double? {\n        for key in keys {\n            if let value = self.parsePercent(dict[key]) {\n                return value\n            }\n        }\n        return nil\n    }\n\n    private static func anyBool(for keys: [String], in dict: [String: Any]) -> Bool? {\n        for key in keys {\n            if let value = self.parseBool(dict[key]) {\n                return value\n            }\n        }\n        return nil\n    }\n\n    private static func findFirstPercent(forKeys keys: [String], in value: Any) -> Double? {\n        if let dict = value as? [String: Any] {\n            for key in keys {\n                if let parsed = self.parsePercent(dict[key]) {\n                    return parsed\n                }\n            }\n            for nested in dict.values {\n                if let parsed = self.findFirstPercent(forKeys: keys, in: nested) {\n                    return parsed\n                }\n            }\n            return nil\n        }\n        if let array = value as? [Any] {\n            for item in array {\n                if let parsed = self.findFirstPercent(forKeys: keys, in: item) {\n                    return parsed\n                }\n            }\n        }\n        return nil\n    }\n\n    private static func parseDate(_ raw: Any?) -> Date? {\n        if let intValue = self.parseInt(raw) {\n            if intValue > 1_000_000_000_000 {\n                return Date(timeIntervalSince1970: TimeInterval(intValue) / 1000)\n            }\n            if intValue > 1_000_000_000 {\n                return Date(timeIntervalSince1970: TimeInterval(intValue))\n            }\n        }\n        if let string = self.parseString(raw) {\n            let formatter = ISO8601DateFormatter()\n            if let date = formatter.date(from: string) {\n                return date\n            }\n            let dateFormatter = DateFormatter()\n            dateFormatter.locale = Locale(identifier: \"en_US_POSIX\")\n            for format in [\"yyyy-MM-dd HH:mm\", \"yyyy-MM-dd HH:mm:ss\"] {\n                dateFormatter.dateFormat = format\n                if let date = dateFormatter.date(from: string) {\n                    return date\n                }\n            }\n        }\n        return nil\n    }\n\n    private static func parseInt(_ raw: Any?) -> Int? {\n        if let value = raw as? Int { return value }\n        if let value = raw as? Int64 { return Int(value) }\n        if let value = raw as? Double { return Int(value) }\n        if let value = raw as? NSNumber { return value.intValue }\n        if let value = raw as? String {\n            let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)\n            return Int(trimmed)\n        }\n        return nil\n    }\n\n    private static func parseString(_ raw: Any?) -> String? {\n        guard let value = raw as? String else { return nil }\n        let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)\n        return trimmed.isEmpty ? nil : trimmed\n    }\n\n    private static func parsePercent(_ raw: Any?) -> Double? {\n        if let intValue = self.parseInt(raw) {\n            return max(0, min(Double(intValue), 100))\n        }\n        guard let rawString = self.parseString(raw) else { return nil }\n        let cleaned = rawString\n            .replacingOccurrences(of: \"%\", with: \"\")\n            .trimmingCharacters(in: .whitespacesAndNewlines)\n        guard let parsed = Double(cleaned) else { return nil }\n        return max(0, min(parsed, 100))\n    }\n\n    private static func parseBool(_ raw: Any?) -> Bool? {\n        if let value = raw as? Bool {\n            return value\n        }\n        if let number = raw as? NSNumber {\n            return number.boolValue\n        }\n        guard let string = self.parseString(raw)?.lowercased() else { return nil }\n        switch string {\n        case \"true\", \"1\", \"yes\", \"active\", \"valid\":\n            return true\n        case \"false\", \"0\", \"no\", \"inactive\", \"invalid\", \"expired\":\n            return false\n        default:\n            return nil\n        }\n    }\n\n    private static func matchFirstGroup(pattern: String, in text: String) -> String? {\n        guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else {\n            return nil\n        }\n        let range = NSRange(text.startIndex..<text.endIndex, in: text)\n        guard let match = regex.firstMatch(in: text, options: [], range: range),\n              match.numberOfRanges > 1,\n              let valueRange = Range(match.range(at: 1), in: text)\n        else {\n            return nil\n        }\n        let value = text[valueRange].trimmingCharacters(in: .whitespacesAndNewlines)\n        return value.isEmpty ? nil : String(value)\n    }\n\n    private static func extractConsoleSECToken(from html: String) -> String? {\n        let patterns = [\n            #\"SEC_TOKEN\\s*:\\s*\\\"([^\\\"]+)\\\"\"#,\n            #\"SEC_TOKEN\\s*:\\s*'([^']+)'\"#,\n            #\"secToken\\s*:\\s*\\\"([^\\\"]+)\\\"\"#,\n            #\"sec_token\\s*:\\s*\\\"([^\\\"]+)\\\"\"#,\n            #\"sec_token\\s*:\\s*'([^']+)'\"#,\n            #\"\\\"SEC_TOKEN\\\"\\s*:\\s*\\\"([^\\\"]+)\\\"\"#,\n            #\"\\\"sec_token\\\"\\s*:\\s*\\\"([^\\\"]+)\\\"\"#,\n        ]\n\n        for pattern in patterns {\n            if let token = self.matchFirstGroup(pattern: pattern, in: html), !token.isEmpty {\n                return token\n            }\n        }\n        return nil\n    }\n\n    private static func extractCookieValue(name: String, from cookieHeader: String) -> String? {\n        let segments = cookieHeader.split(separator: \";\")\n        for segment in segments {\n            let part = segment.trimmingCharacters(in: .whitespacesAndNewlines)\n            guard let index = part.firstIndex(of: \"=\") else { continue }\n            let key = String(part[..<index]).trimmingCharacters(in: .whitespacesAndNewlines)\n            if key == name {\n                let value = String(part[part.index(after: index)...]).trimmingCharacters(in: .whitespacesAndNewlines)\n                return value.isEmpty ? nil : value\n            }\n        }\n        return nil\n    }\n}\n\npublic enum AlibabaCodingPlanUsageError: LocalizedError, Sendable, Equatable {\n    case loginRequired\n    case invalidCredentials\n    case networkError(String)\n    case apiError(String)\n    case parseFailed(String)\n\n    var shouldRetryOnAlternateRegion: Bool {\n        switch self {\n        case .loginRequired:\n            true\n        case .invalidCredentials:\n            true\n        case let .apiError(message):\n            message.contains(\"HTTP 404\") || message.contains(\"HTTP 403\")\n        case let .parseFailed(message):\n            message.contains(\"Missing coding plan quota data\") || message.contains(\"No quota windows found\")\n        case .networkError:\n            false\n        }\n    }\n\n    public var errorDescription: String? {\n        switch self {\n        case .loginRequired:\n            \"Alibaba Coding Plan console login is required. \" +\n                \"Sign in to Model Studio in a supported browser or paste a Cookie header.\"\n        case .invalidCredentials:\n            \"Alibaba Coding Plan API credentials are invalid or expired.\"\n        case let .networkError(message):\n            \"Alibaba Coding Plan network error: \\(message)\"\n        case let .apiError(message):\n            \"Alibaba Coding Plan API error: \\(message)\"\n        case let .parseFailed(message):\n            \"Failed to parse Alibaba Coding Plan response: \\(message)\"\n        }\n    }\n}\n\n// swiftlint:enable type_body_length\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Alibaba/AlibabaCodingPlanUsageSnapshot.swift",
    "content": "import Foundation\n\npublic struct AlibabaCodingPlanUsageSnapshot: Sendable {\n    public let planName: String?\n    public let fiveHourUsedQuota: Int?\n    public let fiveHourTotalQuota: Int?\n    public let fiveHourNextRefreshTime: Date?\n    public let weeklyUsedQuota: Int?\n    public let weeklyTotalQuota: Int?\n    public let weeklyNextRefreshTime: Date?\n    public let monthlyUsedQuota: Int?\n    public let monthlyTotalQuota: Int?\n    public let monthlyNextRefreshTime: Date?\n    public let updatedAt: Date\n\n    public init(\n        planName: String?,\n        fiveHourUsedQuota: Int?,\n        fiveHourTotalQuota: Int?,\n        fiveHourNextRefreshTime: Date?,\n        weeklyUsedQuota: Int?,\n        weeklyTotalQuota: Int?,\n        weeklyNextRefreshTime: Date?,\n        monthlyUsedQuota: Int?,\n        monthlyTotalQuota: Int?,\n        monthlyNextRefreshTime: Date?,\n        updatedAt: Date)\n    {\n        self.planName = planName\n        self.fiveHourUsedQuota = fiveHourUsedQuota\n        self.fiveHourTotalQuota = fiveHourTotalQuota\n        self.fiveHourNextRefreshTime = fiveHourNextRefreshTime\n        self.weeklyUsedQuota = weeklyUsedQuota\n        self.weeklyTotalQuota = weeklyTotalQuota\n        self.weeklyNextRefreshTime = weeklyNextRefreshTime\n        self.monthlyUsedQuota = monthlyUsedQuota\n        self.monthlyTotalQuota = monthlyTotalQuota\n        self.monthlyNextRefreshTime = monthlyNextRefreshTime\n        self.updatedAt = updatedAt\n    }\n}\n\nextension AlibabaCodingPlanUsageSnapshot {\n    public func toUsageSnapshot() -> UsageSnapshot {\n        let primaryPercent = Self.usedPercent(used: self.fiveHourUsedQuota, total: self.fiveHourTotalQuota)\n        let secondaryPercent = Self.usedPercent(used: self.weeklyUsedQuota, total: self.weeklyTotalQuota)\n        let tertiaryPercent = Self.usedPercent(used: self.monthlyUsedQuota, total: self.monthlyTotalQuota)\n\n        let primary: RateWindow? = if let primaryPercent {\n            RateWindow(\n                usedPercent: primaryPercent,\n                windowMinutes: 5 * 60,\n                resetsAt: Self.normalizedFiveHourReset(\n                    self.fiveHourNextRefreshTime,\n                    updatedAt: self.updatedAt),\n                resetDescription: Self.usageDetail(used: self.fiveHourUsedQuota, total: self.fiveHourTotalQuota))\n        } else {\n            nil\n        }\n\n        let secondary: RateWindow? = if let secondaryPercent {\n            RateWindow(\n                usedPercent: secondaryPercent,\n                windowMinutes: 7 * 24 * 60,\n                resetsAt: self.weeklyNextRefreshTime,\n                resetDescription: Self.usageDetail(used: self.weeklyUsedQuota, total: self.weeklyTotalQuota))\n        } else {\n            nil\n        }\n\n        let tertiary: RateWindow? = if let tertiaryPercent {\n            RateWindow(\n                usedPercent: tertiaryPercent,\n                windowMinutes: 30 * 24 * 60,\n                resetsAt: self.monthlyNextRefreshTime,\n                resetDescription: Self.usageDetail(used: self.monthlyUsedQuota, total: self.monthlyTotalQuota))\n        } else {\n            nil\n        }\n\n        let loginMethod = self.planName?.trimmingCharacters(in: .whitespacesAndNewlines)\n        let identity = ProviderIdentitySnapshot(\n            providerID: .alibaba,\n            accountEmail: nil,\n            accountOrganization: nil,\n            loginMethod: loginMethod)\n\n        return UsageSnapshot(\n            primary: primary,\n            secondary: secondary,\n            tertiary: tertiary,\n            providerCost: nil,\n            updatedAt: self.updatedAt,\n            identity: identity)\n    }\n\n    private static func usedPercent(used: Int?, total: Int?) -> Double? {\n        guard let used, let total, total > 0 else { return nil }\n        let normalizedUsed = max(0, min(used, total))\n        return Double(normalizedUsed) / Double(total) * 100\n    }\n\n    private static func limitDescription(total: Int?, label: String) -> String? {\n        guard let total, total > 0 else { return nil }\n        return \"\\(total) requests / \\(label)\"\n    }\n\n    private static func usageDetail(used: Int?, total: Int?) -> String? {\n        guard let used, let total, total > 0 else { return nil }\n        return \"\\(used) / \\(total) used\"\n    }\n\n    private static func normalizedFiveHourReset(_ raw: Date?, updatedAt: Date) -> Date? {\n        guard let raw else { return nil }\n        if raw.timeIntervalSince(updatedAt) >= 60 {\n            return raw\n        }\n\n        let shifted = raw.addingTimeInterval(TimeInterval(5 * 60 * 60))\n        if shifted.timeIntervalSince(updatedAt) >= 60 {\n            return shifted\n        }\n\n        return updatedAt.addingTimeInterval(TimeInterval(5 * 60 * 60))\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Amp/AmpProviderDescriptor.swift",
    "content": "import CodexBarMacroSupport\nimport Foundation\n\n@ProviderDescriptorRegistration\n@ProviderDescriptorDefinition\npublic enum AmpProviderDescriptor {\n    static func makeDescriptor() -> ProviderDescriptor {\n        ProviderDescriptor(\n            id: .amp,\n            metadata: ProviderMetadata(\n                id: .amp,\n                displayName: \"Amp\",\n                sessionLabel: \"Amp Free\",\n                weeklyLabel: \"Balance\",\n                opusLabel: nil,\n                supportsOpus: false,\n                supportsCredits: false,\n                creditsHint: \"\",\n                toggleTitle: \"Show Amp usage\",\n                cliName: \"amp\",\n                defaultEnabled: false,\n                isPrimaryProvider: false,\n                usesAccountFallback: false,\n                browserCookieOrder: ProviderBrowserCookieDefaults.defaultImportOrder,\n                dashboardURL: \"https://ampcode.com/settings\",\n                statusPageURL: nil),\n            branding: ProviderBranding(\n                iconStyle: .amp,\n                iconResourceName: \"ProviderIcon-amp\",\n                color: ProviderColor(red: 220 / 255, green: 38 / 255, blue: 38 / 255)),\n            tokenCost: ProviderTokenCostConfig(\n                supportsTokenCost: false,\n                noDataMessage: { \"Amp cost summary is not supported.\" }),\n            fetchPlan: ProviderFetchPlan(\n                sourceModes: [.auto, .web],\n                pipeline: ProviderFetchPipeline(resolveStrategies: { _ in [AmpStatusFetchStrategy()] })),\n            cli: ProviderCLIConfig(\n                name: \"amp\",\n                versionDetector: nil))\n    }\n}\n\nstruct AmpStatusFetchStrategy: ProviderFetchStrategy {\n    let id: String = \"amp.web\"\n    let kind: ProviderFetchKind = .web\n\n    func isAvailable(_ context: ProviderFetchContext) async -> Bool {\n        guard context.settings?.amp?.cookieSource != .off else { return false }\n        return true\n    }\n\n    func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {\n        let fetcher = AmpUsageFetcher(browserDetection: context.browserDetection)\n        let manual = Self.manualCookieHeader(from: context)\n        let logger: ((String) -> Void)? = context.verbose\n            ? { msg in CodexBarLog.logger(LogCategories.amp).verbose(msg) }\n            : nil\n        let snap = try await fetcher.fetch(cookieHeaderOverride: manual, logger: logger)\n        return self.makeResult(\n            usage: snap.toUsageSnapshot(now: snap.updatedAt),\n            sourceLabel: \"web\")\n    }\n\n    func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool {\n        false\n    }\n\n    private static func manualCookieHeader(from context: ProviderFetchContext) -> String? {\n        guard context.settings?.amp?.cookieSource == .manual else { return nil }\n        return CookieHeaderNormalizer.normalize(context.settings?.amp?.manualCookieHeader)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Amp/AmpUsageFetcher.swift",
    "content": "import Foundation\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n\n#if os(macOS)\nimport SweetCookieKit\n#endif\n\npublic enum AmpUsageError: LocalizedError, Sendable {\n    case notLoggedIn\n    case invalidCredentials\n    case parseFailed(String)\n    case networkError(String)\n    case noSessionCookie\n\n    public var errorDescription: String? {\n        switch self {\n        case .notLoggedIn:\n            \"Not logged in to Amp. Please log in via ampcode.com.\"\n        case .invalidCredentials:\n            \"Amp session cookie expired. Please log in again.\"\n        case let .parseFailed(message):\n            \"Could not parse Amp usage: \\(message)\"\n        case let .networkError(message):\n            \"Amp request failed: \\(message)\"\n        case .noSessionCookie:\n            \"No Amp session cookie found. Please log in to ampcode.com in your browser.\"\n        }\n    }\n}\n\n#if os(macOS)\nprivate let ampCookieImportOrder: BrowserCookieImportOrder =\n    ProviderDefaults.metadata[.amp]?.browserCookieOrder ?? Browser.defaultImportOrder\n\npublic enum AmpCookieImporter {\n    private static let cookieClient = BrowserCookieClient()\n    private static let cookieDomains = [\"ampcode.com\", \"www.ampcode.com\"]\n    private static let sessionCookieNames: Set<String> = [\n        \"session\",\n    ]\n\n    public struct SessionInfo: Sendable {\n        public let cookies: [HTTPCookie]\n        public let sourceLabel: String\n\n        public init(cookies: [HTTPCookie], sourceLabel: String) {\n            self.cookies = cookies\n            self.sourceLabel = sourceLabel\n        }\n\n        public var cookieHeader: String {\n            self.cookies.map { \"\\($0.name)=\\($0.value)\" }.joined(separator: \"; \")\n        }\n    }\n\n    public static func importSession(\n        browserDetection: BrowserDetection,\n        logger: ((String) -> Void)? = nil) throws -> SessionInfo\n    {\n        let log: (String) -> Void = { msg in logger?(\"[amp-cookie] \\(msg)\") }\n\n        let installed = ampCookieImportOrder.cookieImportCandidates(using: browserDetection)\n        for browserSource in installed {\n            do {\n                let query = BrowserCookieQuery(domains: self.cookieDomains)\n                let sources = try Self.cookieClient.records(\n                    matching: query,\n                    in: browserSource,\n                    logger: log)\n                for source in sources where !source.records.isEmpty {\n                    let cookies = BrowserCookieClient.makeHTTPCookies(source.records, origin: query.origin)\n                    guard !cookies.isEmpty else { continue }\n                    let names = cookies.map(\\.name).joined(separator: \", \")\n                    log(\"\\(source.label) cookies: \\(names)\")\n                    let sessionCookies = cookies.filter { Self.sessionCookieNames.contains($0.name) }\n                    if !sessionCookies.isEmpty {\n                        log(\"Found Amp session cookie in \\(source.label)\")\n                        return SessionInfo(cookies: sessionCookies, sourceLabel: source.label)\n                    }\n                    log(\"\\(source.label) cookies found, but no Amp session cookie present\")\n                    log(\"Expected one of: \\(Self.sessionCookieNames.joined(separator: \", \"))\")\n                }\n            } catch {\n                BrowserCookieAccessGate.recordIfNeeded(error)\n                log(\"\\(browserSource.displayName) cookie import failed: \\(error.localizedDescription)\")\n            }\n        }\n\n        throw AmpUsageError.noSessionCookie\n    }\n}\n#endif\n\npublic struct AmpUsageFetcher: Sendable {\n    private static let settingsURL = URL(string: \"https://ampcode.com/settings\")!\n    @MainActor private static var recentDumps: [String] = []\n\n    public let browserDetection: BrowserDetection\n\n    public init(browserDetection: BrowserDetection) {\n        self.browserDetection = browserDetection\n    }\n\n    public func fetch(\n        cookieHeaderOverride: String? = nil,\n        logger: ((String) -> Void)? = nil,\n        now: Date = Date()) async throws -> AmpUsageSnapshot\n    {\n        let log: (String) -> Void = { msg in logger?(\"[amp] \\(msg)\") }\n        let cookieHeader = try await self.resolveCookieHeader(override: cookieHeaderOverride, logger: log)\n\n        if let logger {\n            let names = self.cookieNames(from: cookieHeader)\n            if !names.isEmpty {\n                logger(\"[amp] Cookie names: \\(names.joined(separator: \", \"))\")\n            }\n            let diagnostics = RedirectDiagnostics(cookieHeader: cookieHeader, logger: logger)\n            do {\n                let (html, responseInfo) = try await self.fetchHTMLWithDiagnostics(\n                    cookieHeader: cookieHeader,\n                    diagnostics: diagnostics)\n                self.logDiagnostics(responseInfo: responseInfo, diagnostics: diagnostics, logger: logger)\n                do {\n                    return try AmpUsageParser.parse(html: html, now: now)\n                } catch {\n                    logger(\"[amp] Parse failed: \\(error.localizedDescription)\")\n                    self.logHTMLHints(html: html, logger: logger)\n                    throw error\n                }\n            } catch {\n                self.logDiagnostics(responseInfo: nil, diagnostics: diagnostics, logger: logger)\n                throw error\n            }\n        }\n\n        let diagnostics = RedirectDiagnostics(cookieHeader: cookieHeader, logger: nil)\n        let (html, _) = try await self.fetchHTMLWithDiagnostics(\n            cookieHeader: cookieHeader,\n            diagnostics: diagnostics)\n        return try AmpUsageParser.parse(html: html, now: now)\n    }\n\n    public func debugRawProbe(cookieHeaderOverride: String? = nil) async -> String {\n        let stamp = ISO8601DateFormatter().string(from: Date())\n        var lines: [String] = []\n        lines.append(\"=== Amp Debug Probe @ \\(stamp) ===\")\n        lines.append(\"\")\n\n        do {\n            let cookieHeader = try await self.resolveCookieHeader(\n                override: cookieHeaderOverride,\n                logger: { msg in lines.append(\"[cookie] \\(msg)\") })\n            let diagnostics = RedirectDiagnostics(cookieHeader: cookieHeader, logger: nil)\n            let cookieNames = CookieHeaderNormalizer.pairs(from: cookieHeader).map(\\.name)\n            lines.append(\"Cookie names: \\(cookieNames.joined(separator: \", \"))\")\n\n            let (snapshot, responseInfo) = try await self.fetchWithDiagnostics(\n                cookieHeader: cookieHeader,\n                diagnostics: diagnostics)\n\n            lines.append(\"\")\n            lines.append(\"Fetch Success\")\n            lines.append(\"Status: \\(responseInfo.statusCode) \\(responseInfo.url)\")\n\n            if !diagnostics.redirects.isEmpty {\n                lines.append(\"\")\n                lines.append(\"Redirects:\")\n                for entry in diagnostics.redirects {\n                    lines.append(\"  \\(entry)\")\n                }\n            }\n\n            lines.append(\"\")\n            lines.append(\"Amp Free:\")\n            lines.append(\"  quota=\\(snapshot.freeQuota)\")\n            lines.append(\"  used=\\(snapshot.freeUsed)\")\n            lines.append(\"  hourlyReplenishment=\\(snapshot.hourlyReplenishment)\")\n            lines.append(\"  windowHours=\\(snapshot.windowHours?.description ?? \"nil\")\")\n\n            let output = lines.joined(separator: \"\\n\")\n            Task { @MainActor in Self.recordDump(output) }\n            return output\n        } catch {\n            lines.append(\"\")\n            lines.append(\"Probe Failed: \\(error.localizedDescription)\")\n            let output = lines.joined(separator: \"\\n\")\n            Task { @MainActor in Self.recordDump(output) }\n            return output\n        }\n    }\n\n    public static func latestDumps() async -> String {\n        await MainActor.run {\n            let result = Self.recentDumps.joined(separator: \"\\n\\n---\\n\\n\")\n            return result.isEmpty ? \"No Amp probe dumps captured yet.\" : result\n        }\n    }\n\n    private func resolveCookieHeader(\n        override: String?,\n        logger: ((String) -> Void)?) async throws -> String\n    {\n        if let override = CookieHeaderNormalizer.normalize(override) {\n            if let sessionHeader = self.sessionCookieHeader(from: override) {\n                logger?(\"[amp] Using manual session cookie\")\n                return sessionHeader\n            }\n            throw AmpUsageError.noSessionCookie\n        }\n        #if os(macOS)\n        let session = try AmpCookieImporter.importSession(browserDetection: self.browserDetection, logger: logger)\n        logger?(\"[amp] Using cookies from \\(session.sourceLabel)\")\n        return session.cookieHeader\n        #else\n        throw AmpUsageError.noSessionCookie\n        #endif\n    }\n\n    private func fetchWithDiagnostics(\n        cookieHeader: String,\n        diagnostics: RedirectDiagnostics,\n        now: Date = Date()) async throws -> (AmpUsageSnapshot, ResponseInfo)\n    {\n        let (html, responseInfo) = try await self.fetchHTMLWithDiagnostics(\n            cookieHeader: cookieHeader,\n            diagnostics: diagnostics)\n        let snapshot = try AmpUsageParser.parse(html: html, now: now)\n        return (snapshot, responseInfo)\n    }\n\n    private func fetchHTMLWithDiagnostics(\n        cookieHeader: String,\n        diagnostics: RedirectDiagnostics) async throws -> (String, ResponseInfo)\n    {\n        var request = URLRequest(url: Self.settingsURL)\n        request.httpMethod = \"GET\"\n        request.setValue(cookieHeader, forHTTPHeaderField: \"Cookie\")\n        request.setValue(\n            \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\",\n            forHTTPHeaderField: \"accept\")\n        request.setValue(\n            \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) \" +\n                \"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36\",\n            forHTTPHeaderField: \"user-agent\")\n        request.setValue(\"en-US,en;q=0.9\", forHTTPHeaderField: \"accept-language\")\n        request.setValue(\"https://ampcode.com\", forHTTPHeaderField: \"origin\")\n        request.setValue(Self.settingsURL.absoluteString, forHTTPHeaderField: \"referer\")\n\n        let session = URLSession(configuration: .ephemeral, delegate: diagnostics, delegateQueue: nil)\n        let (data, response) = try await session.data(for: request)\n        guard let httpResponse = response as? HTTPURLResponse else {\n            throw AmpUsageError.networkError(\"Invalid response\")\n        }\n        let responseInfo = ResponseInfo(\n            statusCode: httpResponse.statusCode,\n            url: httpResponse.url?.absoluteString ?? \"unknown\")\n\n        guard httpResponse.statusCode == 200 else {\n            if httpResponse.statusCode == 401 || httpResponse.statusCode == 403 {\n                throw AmpUsageError.invalidCredentials\n            }\n            if diagnostics.detectedLoginRedirect {\n                throw AmpUsageError.invalidCredentials\n            }\n            throw AmpUsageError.networkError(\"HTTP \\(httpResponse.statusCode)\")\n        }\n\n        let html = String(data: data, encoding: .utf8) ?? \"\"\n        return (html, responseInfo)\n    }\n\n    @MainActor private static func recordDump(_ text: String) {\n        if self.recentDumps.count >= 5 { self.recentDumps.removeFirst() }\n        self.recentDumps.append(text)\n    }\n\n    private final class RedirectDiagnostics: NSObject, URLSessionTaskDelegate, @unchecked Sendable {\n        private let cookieHeader: String\n        private let logger: ((String) -> Void)?\n        var redirects: [String] = []\n        private(set) var detectedLoginRedirect = false\n\n        init(cookieHeader: String, logger: ((String) -> Void)?) {\n            self.cookieHeader = cookieHeader\n            self.logger = logger\n        }\n\n        func urlSession(\n            _: URLSession,\n            task: URLSessionTask,\n            willPerformHTTPRedirection response: HTTPURLResponse,\n            newRequest request: URLRequest,\n            completionHandler: @escaping (URLRequest?) -> Void)\n        {\n            let from = response.url?.absoluteString ?? \"unknown\"\n            let to = request.url?.absoluteString ?? \"unknown\"\n            self.redirects.append(\"\\(response.statusCode) \\(from) -> \\(to)\")\n\n            if let toURL = request.url, AmpUsageFetcher.isLoginRedirect(toURL) {\n                if let logger {\n                    logger(\"[amp] Detected login redirect, aborting (invalid session)\")\n                }\n                self.detectedLoginRedirect = true\n                completionHandler(nil)\n                return\n            }\n\n            var updated = request\n            if AmpUsageFetcher.shouldAttachCookie(to: request.url), !self.cookieHeader.isEmpty {\n                updated.setValue(self.cookieHeader, forHTTPHeaderField: \"Cookie\")\n            } else {\n                updated.setValue(nil, forHTTPHeaderField: \"Cookie\")\n            }\n            if let referer = response.url?.absoluteString {\n                updated.setValue(referer, forHTTPHeaderField: \"referer\")\n            }\n            if let logger {\n                logger(\"[amp] Redirect \\(response.statusCode) \\(from) -> \\(to)\")\n            }\n            completionHandler(updated)\n        }\n    }\n\n    private struct ResponseInfo {\n        let statusCode: Int\n        let url: String\n    }\n\n    private func logDiagnostics(\n        responseInfo: ResponseInfo?,\n        diagnostics: RedirectDiagnostics,\n        logger: (String) -> Void)\n    {\n        if let responseInfo {\n            logger(\"[amp] Response: \\(responseInfo.statusCode) \\(responseInfo.url)\")\n        }\n        if !diagnostics.redirects.isEmpty {\n            logger(\"[amp] Redirects:\")\n            for entry in diagnostics.redirects {\n                logger(\"[amp]   \\(entry)\")\n            }\n        }\n    }\n\n    private func logHTMLHints(html: String, logger: (String) -> Void) {\n        let trimmed = html\n            .replacingOccurrences(of: \"\\n\", with: \" \")\n            .replacingOccurrences(of: \"\\t\", with: \" \")\n            .trimmingCharacters(in: .whitespacesAndNewlines)\n        if !trimmed.isEmpty {\n            let snippet = trimmed.prefix(240)\n            logger(\"[amp] HTML snippet: \\(snippet)\")\n        }\n        logger(\"[amp] Contains freeTierUsage: \\(html.contains(\"freeTierUsage\"))\")\n        logger(\"[amp] Contains getFreeTierUsage: \\(html.contains(\"getFreeTierUsage\"))\")\n    }\n\n    private func cookieNames(from header: String) -> [String] {\n        header.split(separator: \";\").compactMap { part in\n            let trimmed = part.trimmingCharacters(in: .whitespacesAndNewlines)\n            guard let idx = trimmed.firstIndex(of: \"=\") else { return nil }\n            let name = trimmed[..<idx].trimmingCharacters(in: .whitespacesAndNewlines)\n            return name.isEmpty ? nil : String(name)\n        }\n    }\n\n    private func sessionCookieHeader(from header: String) -> String? {\n        let pairs = CookieHeaderNormalizer.pairs(from: header)\n        let sessionPairs = pairs.filter { $0.name == \"session\" }\n        guard !sessionPairs.isEmpty else { return nil }\n        return sessionPairs.map { \"\\($0.name)=\\($0.value)\" }.joined(separator: \"; \")\n    }\n\n    static func shouldAttachCookie(to url: URL?) -> Bool {\n        guard let host = url?.host?.lowercased() else { return false }\n        if host == \"ampcode.com\" || host == \"www.ampcode.com\" { return true }\n        return host.hasSuffix(\".ampcode.com\")\n    }\n\n    static func isLoginRedirect(_ url: URL) -> Bool {\n        guard self.shouldAttachCookie(to: url) else { return false }\n\n        let path = url.path.lowercased()\n        let components = path.split(separator: \"/\").map(String.init)\n        if components.contains(\"login\") { return true }\n        if components.contains(\"signin\") { return true }\n        if components.contains(\"sign-in\") { return true }\n\n        // Amp currently redirects to /auth/sign-in?returnTo=... when session is invalid. Keep this slightly broader\n        // than one exact path so we keep working if Amp changes auth routes.\n        if components.contains(\"auth\") {\n            let query = url.query?.lowercased() ?? \"\"\n            if query.contains(\"returnto=\") { return true }\n            if query.contains(\"redirect=\") { return true }\n            if query.contains(\"redirectto=\") { return true }\n        }\n\n        return false\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Amp/AmpUsageParser.swift",
    "content": "import Foundation\n\nenum AmpUsageParser {\n    static func parse(html: String, now: Date = Date()) throws -> AmpUsageSnapshot {\n        guard let usage = self.parseFreeTierUsage(html) else {\n            if self.looksSignedOut(html) {\n                throw AmpUsageError.notLoggedIn\n            }\n            throw AmpUsageError.parseFailed(\"Missing Amp Free usage data.\")\n        }\n\n        return AmpUsageSnapshot(\n            freeQuota: usage.quota,\n            freeUsed: usage.used,\n            hourlyReplenishment: usage.hourlyReplenishment,\n            windowHours: usage.windowHours,\n            updatedAt: now)\n    }\n\n    private struct FreeTierUsage {\n        let quota: Double\n        let used: Double\n        let hourlyReplenishment: Double\n        let windowHours: Double?\n    }\n\n    private static func parseFreeTierUsage(_ html: String) -> FreeTierUsage? {\n        let tokens = [\"freeTierUsage\", \"getFreeTierUsage\"]\n        for token in tokens {\n            if let object = self.extractObject(named: token, in: html),\n               let usage = self.parseFreeTierUsageObject(object)\n            {\n                return usage\n            }\n        }\n        return nil\n    }\n\n    private static func parseFreeTierUsageObject(_ object: String) -> FreeTierUsage? {\n        guard let quota = self.number(for: \"quota\", in: object),\n              let used = self.number(for: \"used\", in: object),\n              let hourly = self.number(for: \"hourlyReplenishment\", in: object)\n        else { return nil }\n\n        let windowHours = self.number(for: \"windowHours\", in: object)\n        return FreeTierUsage(\n            quota: quota,\n            used: used,\n            hourlyReplenishment: hourly,\n            windowHours: windowHours)\n    }\n\n    private static func extractObject(named token: String, in text: String) -> String? {\n        guard let tokenRange = text.range(of: token) else { return nil }\n        guard let braceIndex = text[tokenRange.upperBound...].firstIndex(of: \"{\") else { return nil }\n\n        var depth = 0\n        var inString = false\n        var isEscaped = false\n        var index = braceIndex\n\n        while index < text.endIndex {\n            let char = text[index]\n            if inString {\n                if isEscaped {\n                    isEscaped = false\n                } else if char == \"\\\\\" {\n                    isEscaped = true\n                } else if char == \"\\\"\" {\n                    inString = false\n                }\n            } else {\n                if char == \"\\\"\" {\n                    inString = true\n                } else if char == \"{\" {\n                    depth += 1\n                } else if char == \"}\" {\n                    depth -= 1\n                    if depth == 0 {\n                        return String(text[braceIndex...index])\n                    }\n                }\n            }\n            index = text.index(after: index)\n        }\n\n        return nil\n    }\n\n    private static func number(for key: String, in text: String) -> Double? {\n        let pattern = \"\\\\b\\(NSRegularExpression.escapedPattern(for: key))\\\\b\\\\s*:\\\\s*([0-9]+(?:\\\\.[0-9]+)?)\"\n        guard let regex = try? NSRegularExpression(pattern: pattern) else { return nil }\n        let range = NSRange(text.startIndex..<text.endIndex, in: text)\n        guard let match = regex.firstMatch(in: text, options: [], range: range),\n              match.numberOfRanges > 1,\n              let valueRange = Range(match.range(at: 1), in: text)\n        else { return nil }\n        return Double(text[valueRange])\n    }\n\n    private static func looksSignedOut(_ html: String) -> Bool {\n        let lower = html.lowercased()\n        if lower.contains(\"sign in\") || lower.contains(\"log in\") || lower.contains(\"login\") {\n            return true\n        }\n        if lower.contains(\"/login\") || lower.contains(\"ampcode.com/login\") {\n            return true\n        }\n        return false\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Amp/AmpUsageSnapshot.swift",
    "content": "import Foundation\n\npublic struct AmpUsageSnapshot: Sendable {\n    public let freeQuota: Double\n    public let freeUsed: Double\n    public let hourlyReplenishment: Double\n    public let windowHours: Double?\n    public let updatedAt: Date\n\n    public init(\n        freeQuota: Double,\n        freeUsed: Double,\n        hourlyReplenishment: Double,\n        windowHours: Double?,\n        updatedAt: Date)\n    {\n        self.freeQuota = freeQuota\n        self.freeUsed = freeUsed\n        self.hourlyReplenishment = hourlyReplenishment\n        self.windowHours = windowHours\n        self.updatedAt = updatedAt\n    }\n}\n\nextension AmpUsageSnapshot {\n    public func toUsageSnapshot(now: Date = Date()) -> UsageSnapshot {\n        let quota = max(0, self.freeQuota)\n        let used = max(0, self.freeUsed)\n        let percent: Double = if quota > 0 {\n            min(100, (used / quota) * 100)\n        } else {\n            0\n        }\n\n        let windowMinutes: Int? = if let hours = self.windowHours, hours > 0 {\n            Int((hours * 60).rounded())\n        } else {\n            nil\n        }\n\n        let resetsAt: Date? = {\n            guard quota > 0, self.hourlyReplenishment > 0 else { return nil }\n            let hoursToFull = used / self.hourlyReplenishment\n            let seconds = max(0, hoursToFull * 3600)\n            return now.addingTimeInterval(seconds)\n        }()\n\n        let primary = RateWindow(\n            usedPercent: percent,\n            windowMinutes: windowMinutes,\n            resetsAt: resetsAt,\n            resetDescription: nil)\n\n        let identity = ProviderIdentitySnapshot(\n            providerID: .amp,\n            accountEmail: nil,\n            accountOrganization: nil,\n            loginMethod: \"Amp Free\")\n\n        return UsageSnapshot(\n            primary: primary,\n            secondary: nil,\n            tertiary: nil,\n            providerCost: nil,\n            updatedAt: self.updatedAt,\n            identity: identity)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Antigravity/AntigravityProviderDescriptor.swift",
    "content": "import CodexBarMacroSupport\nimport Foundation\n\n@ProviderDescriptorRegistration\n@ProviderDescriptorDefinition\npublic enum AntigravityProviderDescriptor {\n    static func makeDescriptor() -> ProviderDescriptor {\n        ProviderDescriptor(\n            id: .antigravity,\n            metadata: ProviderMetadata(\n                id: .antigravity,\n                displayName: \"Antigravity\",\n                sessionLabel: \"Claude\",\n                weeklyLabel: \"Gemini Pro\",\n                opusLabel: \"Gemini Flash\",\n                supportsOpus: true,\n                supportsCredits: false,\n                creditsHint: \"\",\n                toggleTitle: \"Show Antigravity usage (experimental)\",\n                cliName: \"antigravity\",\n                defaultEnabled: false,\n                isPrimaryProvider: false,\n                usesAccountFallback: false,\n                dashboardURL: nil,\n                statusPageURL: nil,\n                statusLinkURL: \"https://www.google.com/appsstatus/dashboard/products/npdyhgECDJ6tB66MxXyo/history\",\n                statusWorkspaceProductID: \"npdyhgECDJ6tB66MxXyo\"),\n            branding: ProviderBranding(\n                iconStyle: .antigravity,\n                iconResourceName: \"ProviderIcon-antigravity\",\n                color: ProviderColor(red: 96 / 255, green: 186 / 255, blue: 126 / 255)),\n            tokenCost: ProviderTokenCostConfig(\n                supportsTokenCost: false,\n                noDataMessage: { \"Antigravity cost summary is not supported.\" }),\n            fetchPlan: ProviderFetchPlan(\n                sourceModes: [.auto, .cli],\n                pipeline: ProviderFetchPipeline(resolveStrategies: { _ in [AntigravityStatusFetchStrategy()] })),\n            cli: ProviderCLIConfig(\n                name: \"antigravity\",\n                versionDetector: nil))\n    }\n}\n\nstruct AntigravityStatusFetchStrategy: ProviderFetchStrategy {\n    let id: String = \"antigravity.local\"\n    let kind: ProviderFetchKind = .localProbe\n\n    func isAvailable(_: ProviderFetchContext) async -> Bool {\n        true\n    }\n\n    func fetch(_: ProviderFetchContext) async throws -> ProviderFetchResult {\n        let probe = AntigravityStatusProbe()\n        let snap = try await probe.fetch()\n        let usage = try snap.toUsageSnapshot()\n        return self.makeResult(\n            usage: usage,\n            sourceLabel: \"local\")\n    }\n\n    func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool {\n        false\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Antigravity/AntigravityStatusProbe.swift",
    "content": "import Foundation\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n\npublic struct AntigravityModelQuota: Sendable {\n    public let label: String\n    public let modelId: String\n    public let remainingFraction: Double?\n    public let resetTime: Date?\n    public let resetDescription: String?\n\n    public var remainingPercent: Double {\n        guard let remainingFraction else { return 0 }\n        return max(0, min(100, remainingFraction * 100))\n    }\n}\n\npublic struct AntigravityStatusSnapshot: Sendable {\n    public let modelQuotas: [AntigravityModelQuota]\n    public let accountEmail: String?\n    public let accountPlan: String?\n\n    public func toUsageSnapshot() throws -> UsageSnapshot {\n        let ordered = Self.selectModels(self.modelQuotas)\n        guard let primaryQuota = ordered.first else {\n            throw AntigravityStatusProbeError.parseFailed(\"No quota models available\")\n        }\n\n        let primary = Self.rateWindow(for: primaryQuota)\n        let secondary = ordered.count > 1 ? Self.rateWindow(for: ordered[1]) : nil\n        let tertiary = ordered.count > 2 ? Self.rateWindow(for: ordered[2]) : nil\n\n        let identity = ProviderIdentitySnapshot(\n            providerID: .antigravity,\n            accountEmail: self.accountEmail,\n            accountOrganization: nil,\n            loginMethod: self.accountPlan)\n        return UsageSnapshot(\n            primary: primary,\n            secondary: secondary,\n            tertiary: tertiary,\n            updatedAt: Date(),\n            identity: identity)\n    }\n\n    private static func rateWindow(for quota: AntigravityModelQuota) -> RateWindow {\n        RateWindow(\n            usedPercent: 100 - quota.remainingPercent,\n            windowMinutes: nil,\n            resetsAt: quota.resetTime,\n            resetDescription: quota.resetDescription)\n    }\n\n    private static func selectModels(_ models: [AntigravityModelQuota]) -> [AntigravityModelQuota] {\n        var ordered: [AntigravityModelQuota] = []\n        if let claude = models.first(where: { Self.isClaudeWithoutThinking($0.label) }) {\n            ordered.append(claude)\n        }\n        if let pro = models.first(where: { Self.isGeminiProLow($0.label) }),\n           !ordered.contains(where: { $0.label == pro.label })\n        {\n            ordered.append(pro)\n        }\n        if let flash = models.first(where: { Self.isGeminiFlash($0.label) }),\n           !ordered.contains(where: { $0.label == flash.label })\n        {\n            ordered.append(flash)\n        }\n        if ordered.isEmpty {\n            ordered.append(contentsOf: models.sorted(by: { $0.remainingPercent < $1.remainingPercent }))\n        }\n        return ordered\n    }\n\n    private static func isClaudeWithoutThinking(_ label: String) -> Bool {\n        let lower = label.lowercased()\n        return lower.contains(\"claude\") && !lower.contains(\"thinking\")\n    }\n\n    private static func isGeminiProLow(_ label: String) -> Bool {\n        let lower = label.lowercased()\n        return lower.contains(\"pro\") && lower.contains(\"low\")\n    }\n\n    private static func isGeminiFlash(_ label: String) -> Bool {\n        let lower = label.lowercased()\n        return lower.contains(\"gemini\") && lower.contains(\"flash\")\n    }\n}\n\npublic struct AntigravityPlanInfoSummary: Sendable, Codable, Equatable {\n    public let planName: String?\n    public let planDisplayName: String?\n    public let displayName: String?\n    public let productName: String?\n    public let planShortName: String?\n}\n\npublic enum AntigravityStatusProbeError: LocalizedError, Sendable, Equatable {\n    case notRunning\n    case missingCSRFToken\n    case portDetectionFailed(String)\n    case apiError(String)\n    case parseFailed(String)\n    case timedOut\n\n    public var errorDescription: String? {\n        switch self {\n        case .notRunning:\n            \"Antigravity language server not detected. Launch Antigravity and retry.\"\n        case .missingCSRFToken:\n            \"Antigravity CSRF token not found. Restart Antigravity and retry.\"\n        case let .portDetectionFailed(message):\n            Self.portDetectionDescription(message)\n        case let .apiError(message):\n            Self.apiErrorDescription(message)\n        case let .parseFailed(message):\n            \"Could not parse Antigravity quota: \\(message)\"\n        case .timedOut:\n            \"Antigravity quota request timed out.\"\n        }\n    }\n\n    private static func portDetectionDescription(_ message: String) -> String {\n        switch message {\n        case \"lsof not available\":\n            \"Antigravity port detection needs lsof. Install it, then retry.\"\n        case \"no listening ports found\":\n            \"Antigravity is running but not exposing ports yet. Try again in a few seconds.\"\n        default:\n            \"Antigravity port detection failed: \\(message)\"\n        }\n    }\n\n    private static func apiErrorDescription(_ message: String) -> String {\n        if message.contains(\"HTTP 401\") || message.contains(\"HTTP 403\") {\n            return \"Antigravity session expired. Restart Antigravity and retry.\"\n        }\n        return \"Antigravity API error: \\(message)\"\n    }\n}\n\npublic struct AntigravityStatusProbe: Sendable {\n    public var timeout: TimeInterval = 8.0\n\n    private static let processName = \"language_server_macos\"\n    private static let getUserStatusPath = \"/exa.language_server_pb.LanguageServerService/GetUserStatus\"\n    private static let commandModelConfigPath =\n        \"/exa.language_server_pb.LanguageServerService/GetCommandModelConfigs\"\n    private static let unleashPath = \"/exa.language_server_pb.LanguageServerService/GetUnleashData\"\n    private static let log = CodexBarLog.logger(LogCategories.antigravity)\n\n    public init(timeout: TimeInterval = 8.0) {\n        self.timeout = timeout\n    }\n\n    public func fetch() async throws -> AntigravityStatusSnapshot {\n        let processInfo = try await Self.detectProcessInfo(timeout: self.timeout)\n        let ports = try await Self.listeningPorts(pid: processInfo.pid, timeout: self.timeout)\n        let connectPort = try await Self.findWorkingPort(\n            ports: ports,\n            csrfToken: processInfo.csrfToken,\n            timeout: self.timeout)\n        let context = RequestContext(\n            httpsPort: connectPort,\n            httpPort: processInfo.extensionPort,\n            csrfToken: processInfo.csrfToken,\n            timeout: self.timeout)\n\n        do {\n            let response = try await Self.makeRequest(\n                payload: RequestPayload(\n                    path: Self.getUserStatusPath,\n                    body: Self.defaultRequestBody()),\n                context: context)\n            return try Self.parseUserStatusResponse(response)\n        } catch {\n            let response = try await Self.makeRequest(\n                payload: RequestPayload(\n                    path: Self.commandModelConfigPath,\n                    body: Self.defaultRequestBody()),\n                context: context)\n            return try Self.parseCommandModelResponse(response)\n        }\n    }\n\n    public func fetchPlanInfoSummary() async throws -> AntigravityPlanInfoSummary? {\n        let processInfo = try await Self.detectProcessInfo(timeout: self.timeout)\n        let ports = try await Self.listeningPorts(pid: processInfo.pid, timeout: self.timeout)\n        let connectPort = try await Self.findWorkingPort(\n            ports: ports,\n            csrfToken: processInfo.csrfToken,\n            timeout: self.timeout)\n        let response = try await Self.makeRequest(\n            payload: RequestPayload(\n                path: Self.getUserStatusPath,\n                body: Self.defaultRequestBody()),\n            context: RequestContext(\n                httpsPort: connectPort,\n                httpPort: processInfo.extensionPort,\n                csrfToken: processInfo.csrfToken,\n                timeout: self.timeout))\n        return try Self.parsePlanInfoSummary(response)\n    }\n\n    public static func isRunning(timeout: TimeInterval = 4.0) async -> Bool {\n        await (try? self.detectProcessInfo(timeout: timeout)) != nil\n    }\n\n    public static func detectVersion(timeout: TimeInterval = 4.0) async -> String? {\n        let running = await Self.isRunning(timeout: timeout)\n        return running ? \"running\" : nil\n    }\n\n    // MARK: - Parsing\n\n    public static func parseUserStatusResponse(_ data: Data) throws -> AntigravityStatusSnapshot {\n        let decoder = JSONDecoder()\n        let response = try decoder.decode(UserStatusResponse.self, from: data)\n        if let invalid = Self.invalidCode(response.code) {\n            throw AntigravityStatusProbeError.apiError(invalid)\n        }\n        guard let userStatus = response.userStatus else {\n            throw AntigravityStatusProbeError.parseFailed(\"Missing userStatus\")\n        }\n\n        let modelConfigs = userStatus.cascadeModelConfigData?.clientModelConfigs ?? []\n        let models = modelConfigs.compactMap(Self.quotaFromConfig(_:))\n        let email = userStatus.email\n        let planName = userStatus.planStatus?.planInfo?.preferredName\n\n        return AntigravityStatusSnapshot(\n            modelQuotas: models,\n            accountEmail: email,\n            accountPlan: planName)\n    }\n\n    static func parsePlanInfoSummary(_ data: Data) throws -> AntigravityPlanInfoSummary? {\n        let decoder = JSONDecoder()\n        let response = try decoder.decode(UserStatusResponse.self, from: data)\n        if let invalid = Self.invalidCode(response.code) {\n            throw AntigravityStatusProbeError.apiError(invalid)\n        }\n        guard let userStatus = response.userStatus else {\n            throw AntigravityStatusProbeError.parseFailed(\"Missing userStatus\")\n        }\n        guard let planInfo = userStatus.planStatus?.planInfo else { return nil }\n        return AntigravityPlanInfoSummary(\n            planName: planInfo.planName,\n            planDisplayName: planInfo.planDisplayName,\n            displayName: planInfo.displayName,\n            productName: planInfo.productName,\n            planShortName: planInfo.planShortName)\n    }\n\n    static func parseCommandModelResponse(_ data: Data) throws -> AntigravityStatusSnapshot {\n        let decoder = JSONDecoder()\n        let response = try decoder.decode(CommandModelConfigResponse.self, from: data)\n        if let invalid = Self.invalidCode(response.code) {\n            throw AntigravityStatusProbeError.apiError(invalid)\n        }\n        let modelConfigs = response.clientModelConfigs ?? []\n        let models = modelConfigs.compactMap(Self.quotaFromConfig(_:))\n        return AntigravityStatusSnapshot(modelQuotas: models, accountEmail: nil, accountPlan: nil)\n    }\n\n    private static func quotaFromConfig(_ config: ModelConfig) -> AntigravityModelQuota? {\n        guard let quota = config.quotaInfo else { return nil }\n        let reset = quota.resetTime.flatMap { Self.parseDate($0) }\n        return AntigravityModelQuota(\n            label: config.label,\n            modelId: config.modelOrAlias.model,\n            remainingFraction: quota.remainingFraction,\n            resetTime: reset,\n            resetDescription: nil)\n    }\n\n    private static func invalidCode(_ code: CodeValue?) -> String? {\n        guard let code else { return nil }\n        if code.isOK { return nil }\n        return \"\\(code.rawValue)\"\n    }\n\n    private static func parseDate(_ value: String) -> Date? {\n        if let date = ISO8601DateFormatter().date(from: value) {\n            return date\n        }\n        if let seconds = Double(value) {\n            return Date(timeIntervalSince1970: seconds)\n        }\n        return nil\n    }\n\n    // MARK: - Port detection\n\n    private struct ProcessInfoResult {\n        let pid: Int\n        let extensionPort: Int?\n        let csrfToken: String\n        let commandLine: String\n    }\n\n    private static func detectProcessInfo(timeout: TimeInterval) async throws -> ProcessInfoResult {\n        let env = ProcessInfo.processInfo.environment\n        let result = try await SubprocessRunner.run(\n            binary: \"/bin/ps\",\n            arguments: [\"-ax\", \"-o\", \"pid=,command=\"],\n            environment: env,\n            timeout: timeout,\n            label: \"antigravity-ps\")\n\n        let lines = result.stdout.split(separator: \"\\n\")\n        var sawAntigravity = false\n        for line in lines {\n            let text = String(line)\n            guard let match = Self.matchProcessLine(text) else { continue }\n            let lower = match.command.lowercased()\n            guard lower.contains(Self.processName) else { continue }\n            guard Self.isAntigravityCommandLine(lower) else { continue }\n            sawAntigravity = true\n            guard let token = Self.extractFlag(\"--csrf_token\", from: match.command) else { continue }\n            let port = Self.extractPort(\"--extension_server_port\", from: match.command)\n            return ProcessInfoResult(pid: match.pid, extensionPort: port, csrfToken: token, commandLine: match.command)\n        }\n\n        if sawAntigravity {\n            throw AntigravityStatusProbeError.missingCSRFToken\n        }\n        throw AntigravityStatusProbeError.notRunning\n    }\n\n    private struct ProcessLineMatch {\n        let pid: Int\n        let command: String\n    }\n\n    private static func matchProcessLine(_ line: String) -> ProcessLineMatch? {\n        let trimmed = line.trimmingCharacters(in: .whitespacesAndNewlines)\n        guard !trimmed.isEmpty else { return nil }\n        let parts = trimmed.split(separator: \" \", maxSplits: 1, omittingEmptySubsequences: true)\n        guard parts.count == 2, let pid = Int(parts[0]) else { return nil }\n        return ProcessLineMatch(pid: pid, command: String(parts[1]))\n    }\n\n    private static func isAntigravityCommandLine(_ command: String) -> Bool {\n        if command.contains(\"--app_data_dir\") && command.contains(\"antigravity\") { return true }\n        if command.contains(\"/antigravity/\") || command.contains(\"\\\\antigravity\\\\\") { return true }\n        return false\n    }\n\n    private static func extractFlag(_ flag: String, from command: String) -> String? {\n        let pattern = \"\\(NSRegularExpression.escapedPattern(for: flag))[=\\\\s]+([^\\\\s]+)\"\n        guard let regex = try? NSRegularExpression(pattern: pattern, options: .caseInsensitive) else { return nil }\n        let range = NSRange(command.startIndex..<command.endIndex, in: command)\n        guard let match = regex.firstMatch(in: command, options: [], range: range),\n              let tokenRange = Range(match.range(at: 1), in: command) else { return nil }\n        return String(command[tokenRange])\n    }\n\n    private static func extractPort(_ flag: String, from command: String) -> Int? {\n        guard let raw = extractFlag(flag, from: command) else { return nil }\n        return Int(raw)\n    }\n\n    private static func listeningPorts(pid: Int, timeout: TimeInterval) async throws -> [Int] {\n        let lsof = [\"/usr/sbin/lsof\", \"/usr/bin/lsof\"].first(where: {\n            FileManager.default.isExecutableFile(atPath: $0)\n        })\n\n        guard let lsof else {\n            throw AntigravityStatusProbeError.portDetectionFailed(\"lsof not available\")\n        }\n\n        let env = ProcessInfo.processInfo.environment\n        let result = try await SubprocessRunner.run(\n            binary: lsof,\n            arguments: [\"-nP\", \"-iTCP\", \"-sTCP:LISTEN\", \"-a\", \"-p\", String(pid)],\n            environment: env,\n            timeout: timeout,\n            label: \"antigravity-lsof\")\n\n        let ports = Self.parseListeningPorts(result.stdout)\n        if ports.isEmpty {\n            throw AntigravityStatusProbeError.portDetectionFailed(\"no listening ports found\")\n        }\n        return ports\n    }\n\n    private static func parseListeningPorts(_ output: String) -> [Int] {\n        guard let regex = try? NSRegularExpression(pattern: #\":(\\d+)\\s+\\(LISTEN\\)\"#) else { return [] }\n        let range = NSRange(output.startIndex..<output.endIndex, in: output)\n        var ports: Set<Int> = []\n        regex.enumerateMatches(in: output, options: [], range: range) { match, _, _ in\n            guard let match,\n                  let range = Range(match.range(at: 1), in: output),\n                  let value = Int(output[range]) else { return }\n            ports.insert(value)\n        }\n        return ports.sorted()\n    }\n\n    private static func findWorkingPort(\n        ports: [Int],\n        csrfToken: String,\n        timeout: TimeInterval) async throws -> Int\n    {\n        for port in ports {\n            let ok = await Self.testPortConnectivity(port: port, csrfToken: csrfToken, timeout: timeout)\n            if ok { return port }\n        }\n        throw AntigravityStatusProbeError.portDetectionFailed(\"no working API port found\")\n    }\n\n    private static func testPortConnectivity(\n        port: Int,\n        csrfToken: String,\n        timeout: TimeInterval) async -> Bool\n    {\n        do {\n            _ = try await self.makeRequest(\n                payload: RequestPayload(\n                    path: self.unleashPath,\n                    body: self.unleashRequestBody()),\n                context: RequestContext(\n                    httpsPort: port,\n                    httpPort: nil,\n                    csrfToken: csrfToken,\n                    timeout: timeout))\n            return true\n        } catch {\n            self.log.debug(\"Port probe failed\", metadata: [\n                \"port\": \"\\(port)\",\n                \"error\": error.localizedDescription,\n            ])\n            return false\n        }\n    }\n\n    // MARK: - HTTP\n\n    private struct RequestPayload {\n        let path: String\n        let body: [String: Any]\n    }\n\n    private struct RequestContext {\n        let httpsPort: Int\n        let httpPort: Int?\n        let csrfToken: String\n        let timeout: TimeInterval\n    }\n\n    private static func defaultRequestBody() -> [String: Any] {\n        [\n            \"metadata\": [\n                \"ideName\": \"antigravity\",\n                \"extensionName\": \"antigravity\",\n                \"ideVersion\": \"unknown\",\n                \"locale\": \"en\",\n            ],\n        ]\n    }\n\n    private static func unleashRequestBody() -> [String: Any] {\n        [\n            \"context\": [\n                \"properties\": [\n                    \"devMode\": \"false\",\n                    \"extensionVersion\": \"unknown\",\n                    \"hasAnthropicModelAccess\": \"true\",\n                    \"ide\": \"antigravity\",\n                    \"ideVersion\": \"unknown\",\n                    \"installationId\": \"codexbar\",\n                    \"language\": \"UNSPECIFIED\",\n                    \"os\": \"macos\",\n                    \"requestedModelId\": \"MODEL_UNSPECIFIED\",\n                ],\n            ],\n        ]\n    }\n\n    private static func makeRequest(\n        payload: RequestPayload,\n        context: RequestContext) async throws -> Data\n    {\n        do {\n            return try await self.sendRequest(\n                scheme: \"https\",\n                port: context.httpsPort,\n                payload: payload,\n                context: context)\n        } catch {\n            guard let httpPort = context.httpPort, httpPort != context.httpsPort else { throw error }\n            return try await Self.sendRequest(\n                scheme: \"http\",\n                port: httpPort,\n                payload: payload,\n                context: context)\n        }\n    }\n\n    private static func sendRequest(\n        scheme: String,\n        port: Int,\n        payload: RequestPayload,\n        context: RequestContext) async throws -> Data\n    {\n        guard let url = URL(string: \"\\(scheme)://127.0.0.1:\\(port)\\(payload.path)\") else {\n            throw AntigravityStatusProbeError.apiError(\"Invalid URL\")\n        }\n\n        let body = try JSONSerialization.data(withJSONObject: payload.body, options: [])\n        var request = URLRequest(url: url)\n        request.httpMethod = \"POST\"\n        request.httpBody = body\n        request.timeoutInterval = context.timeout\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Content-Type\")\n        request.setValue(String(body.count), forHTTPHeaderField: \"Content-Length\")\n        request.setValue(\"1\", forHTTPHeaderField: \"Connect-Protocol-Version\")\n        request.setValue(context.csrfToken, forHTTPHeaderField: \"X-Codeium-Csrf-Token\")\n\n        let config = URLSessionConfiguration.ephemeral\n        config.timeoutIntervalForRequest = context.timeout\n        config.timeoutIntervalForResource = context.timeout\n        let session = URLSession(configuration: config, delegate: InsecureSessionDelegate(), delegateQueue: nil)\n        defer { session.invalidateAndCancel() }\n\n        let (data, response) = try await session.data(for: request)\n        guard let http = response as? HTTPURLResponse else {\n            throw AntigravityStatusProbeError.apiError(\"Invalid response\")\n        }\n        guard http.statusCode == 200 else {\n            let message = String(data: data, encoding: .utf8) ?? \"\"\n            throw AntigravityStatusProbeError.apiError(\"HTTP \\(http.statusCode): \\(message)\")\n        }\n        return data\n    }\n}\n\nprivate final class InsecureSessionDelegate: NSObject {}\n\nextension InsecureSessionDelegate: URLSessionTaskDelegate {}\n\nextension InsecureSessionDelegate {\n    func urlSession(\n        _ session: URLSession,\n        task: URLSessionTask,\n        didReceive challenge: URLAuthenticationChallenge,\n        completionHandler: @escaping @MainActor @Sendable (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)\n    {\n        let result = self.challengeResult(challenge)\n        Task { @MainActor in\n            completionHandler(result.disposition, result.credential)\n        }\n    }\n\n    private func challengeResult(_ challenge: URLAuthenticationChallenge) -> (\n        disposition: URLSession.AuthChallengeDisposition,\n        credential: URLCredential?)\n    {\n        #if canImport(FoundationNetworking)\n        return (.performDefaultHandling, nil)\n        #else\n        if let trust = challenge.protectionSpace.serverTrust {\n            return (.useCredential, URLCredential(trust: trust))\n        }\n        return (.performDefaultHandling, nil)\n        #endif\n    }\n}\n\nprivate struct UserStatusResponse: Decodable {\n    let code: CodeValue?\n    let message: String?\n    let userStatus: UserStatus?\n}\n\nprivate struct CommandModelConfigResponse: Decodable {\n    let code: CodeValue?\n    let message: String?\n    let clientModelConfigs: [ModelConfig]?\n}\n\nprivate struct UserStatus: Decodable {\n    let email: String?\n    let planStatus: PlanStatus?\n    let cascadeModelConfigData: ModelConfigData?\n}\n\nprivate struct PlanStatus: Decodable {\n    let planInfo: PlanInfo?\n}\n\nprivate struct PlanInfo: Decodable {\n    let planName: String?\n    let planDisplayName: String?\n    let displayName: String?\n    let productName: String?\n    let planShortName: String?\n\n    var preferredName: String? {\n        let candidates = [\n            planDisplayName,\n            displayName,\n            productName,\n            planName,\n            planShortName,\n        ]\n        for candidate in candidates {\n            guard let value = candidate?.trimmingCharacters(in: .whitespacesAndNewlines) else { continue }\n            if !value.isEmpty { return value }\n        }\n        return nil\n    }\n}\n\nprivate struct ModelConfigData: Decodable {\n    let clientModelConfigs: [ModelConfig]?\n}\n\nprivate struct ModelConfig: Decodable {\n    let label: String\n    let modelOrAlias: ModelAlias\n    let quotaInfo: QuotaInfo?\n}\n\nprivate struct ModelAlias: Decodable {\n    let model: String\n}\n\nprivate struct QuotaInfo: Decodable {\n    let remainingFraction: Double?\n    let resetTime: String?\n}\n\nprivate enum CodeValue: Decodable {\n    case int(Int)\n    case string(String)\n\n    var isOK: Bool {\n        switch self {\n        case let .int(value):\n            return value == 0\n        case let .string(value):\n            let lower = value.lowercased()\n            return lower == \"ok\" || lower == \"success\" || value == \"0\"\n        }\n    }\n\n    var rawValue: String {\n        switch self {\n        case let .int(value): \"\\(value)\"\n        case let .string(value): value\n        }\n    }\n\n    init(from decoder: Decoder) throws {\n        let container = try decoder.singleValueContainer()\n        if let value = try? container.decode(Int.self) {\n            self = .int(value)\n            return\n        }\n        if let value = try? container.decode(String.self) {\n            self = .string(value)\n            return\n        }\n        throw DecodingError.dataCorruptedError(in: container, debugDescription: \"Unsupported code type\")\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Augment/AuggieCLIProbe.swift",
    "content": "import Foundation\n\n#if os(macOS)\n\n/// Fetches Augment usage via `auggie account status` CLI command\npublic struct AuggieCLIProbe: Sendable {\n    private static let log = CodexBarLog.logger(LogCategories.auggieCLI)\n\n    public init() {}\n\n    public func fetch() async throws -> AugmentStatusSnapshot {\n        let output = try await self.runAuggieAccountStatus()\n        return try self.parse(output)\n    }\n\n    /// Timeout for the `auggie account status` command.\n    private static let commandTimeout: TimeInterval = 15\n\n    private func runAuggieAccountStatus() async throws -> String {\n        let env = ProcessInfo.processInfo.environment\n        let loginPATH = LoginShellPathCache.shared.current\n        let executable = BinaryLocator.resolveAuggieBinary(env: env, loginPATH: loginPATH) ?? \"auggie\"\n\n        var pathEnv = env\n        pathEnv[\"PATH\"] = PathBuilder.effectivePATH(\n            purposes: [.tty, .nodeTooling],\n            env: env,\n            loginPATH: loginPATH)\n\n        let result = try await SubprocessRunner.run(\n            binary: executable,\n            arguments: [\"account\", \"status\"],\n            environment: pathEnv,\n            timeout: Self.commandTimeout,\n            label: \"auggie-account-status\")\n\n        let output = result.stdout\n        let errorOutput = result.stderr\n\n        guard !output.isEmpty else {\n            if !errorOutput.isEmpty {\n                Self.log.error(\"Auggie stderr: \\(errorOutput)\")\n            }\n            throw AuggieCLIError.noOutput\n        }\n\n        // Check for auth errors\n        if output.contains(\"Authentication failed\") || output.contains(\"auggie login\") {\n            throw AuggieCLIError.notAuthenticated\n        }\n\n        return output\n    }\n\n    private func parse(_ output: String) throws -> AugmentStatusSnapshot {\n        // Parse output like:\n        // Max Plan 450,000 credits / month\n        // 11,657 remaining · 953,170 / 964,827 credits used\n        // 2 days remaining in this billing cycle (ends 1/8/2026)\n\n        var maxCredits: Int?\n        var remaining: Int?\n        var used: Int?\n        var total: Int?\n        var billingCycleEnd: Date?\n\n        for line in output.split(separator: \"\\n\") {\n            let trimmed = line.trimmingCharacters(in: .whitespaces)\n\n            // Parse \"Max Plan 450,000 credits / month\"\n            if trimmed.contains(\"Max Plan\"), trimmed.contains(\"credits\") {\n                if let match = trimmed.range(of: #\"([\\d,]+)\\s+credits\"#, options: .regularExpression) {\n                    let numberStr = String(trimmed[match]).replacingOccurrences(of: \",\", with: \"\")\n                        .replacingOccurrences(of: \" credits\", with: \"\")\n                    maxCredits = Int(numberStr)\n                }\n            }\n\n            // Parse \"11,657 remaining · 953,170 / 964,827 credits used\"\n            if trimmed.contains(\"remaining\"), trimmed.contains(\"credits used\") {\n                // Extract remaining\n                if let remMatch = trimmed.range(of: #\"([\\d,]+)\\s+remaining\"#, options: .regularExpression) {\n                    let numStr = String(trimmed[remMatch])\n                        .replacingOccurrences(of: \",\", with: \"\")\n                        .replacingOccurrences(of: \" remaining\", with: \"\")\n                    remaining = Int(numStr)\n                }\n\n                // Extract used / total\n                if let usedMatch = trimmed.range(\n                    of: #\"([\\d,]+)\\s*/\\s*([\\d,]+)\\s+credits used\"#,\n                    options: .regularExpression)\n                {\n                    let parts = String(trimmed[usedMatch])\n                        .replacingOccurrences(of: \" credits used\", with: \"\")\n                        .split(separator: \"/\")\n                    if parts.count == 2 {\n                        used = Int(parts[0].replacingOccurrences(of: \",\", with: \"\")\n                            .trimmingCharacters(in: .whitespaces))\n                        total = Int(parts[1].replacingOccurrences(of: \",\", with: \"\")\n                            .trimmingCharacters(in: .whitespaces))\n                    }\n                }\n            }\n\n            // Parse \"2 days remaining in this billing cycle (ends 1/8/2026)\"\n            if trimmed.contains(\"billing cycle\"), trimmed.contains(\"ends\") {\n                // Extract date from \"(ends 1/8/2026)\"\n                if let dateMatch = trimmed.range(of: #\"ends\\s+([\\d/]+)\"#, options: .regularExpression) {\n                    let dateStr = String(trimmed[dateMatch])\n                        .replacingOccurrences(of: \"ends\", with: \"\")\n                        .trimmingCharacters(in: .whitespaces)\n\n                    // Parse date like \"1/8/2026\"\n                    let formatter = DateFormatter()\n                    formatter.dateFormat = \"M/d/yyyy\"\n                    formatter.locale = Locale(identifier: \"en_US_POSIX\")\n                    formatter.timeZone = TimeZone.current\n                    billingCycleEnd = formatter.date(from: dateStr)\n                }\n            }\n        }\n\n        guard let finalRemaining = remaining, let finalUsed = used, let finalTotal = total else {\n            Self.log.error(\"Failed to parse auggie output: \\(output)\")\n            throw AuggieCLIError.parseError(\"Could not extract credits from output\")\n        }\n\n        return AugmentStatusSnapshot(\n            creditsRemaining: Double(finalRemaining),\n            creditsUsed: Double(finalUsed),\n            creditsLimit: Double(finalTotal),\n            billingCycleEnd: billingCycleEnd,\n            accountEmail: nil,\n            accountPlan: maxCredits.map { \"\\($0.formatted()) credits/month\" },\n            rawJSON: nil)\n    }\n}\n\n#else\n\npublic struct AuggieCLIProbe: Sendable {\n    public init() {}\n\n    public func fetch() async throws -> AugmentStatusSnapshot {\n        throw AugmentStatusProbeError.notSupported\n    }\n}\n\n#endif\n\npublic enum AuggieCLIError: LocalizedError {\n    case noOutput\n    case notAuthenticated\n    case parseError(String)\n\n    public var errorDescription: String? {\n        switch self {\n        case .noOutput:\n            \"Auggie CLI returned no output\"\n        case .notAuthenticated:\n            \"Not authenticated. Run 'auggie login' to authenticate.\"\n        case let .parseError(msg):\n            \"Failed to parse auggie output: \\(msg)\"\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Augment/AugmentProviderDescriptor.swift",
    "content": "import CodexBarMacroSupport\nimport Foundation\n\n#if os(macOS)\nimport SweetCookieKit\n#endif\n\n@ProviderDescriptorRegistration\n@ProviderDescriptorDefinition\npublic enum AugmentProviderDescriptor {\n    static func makeDescriptor() -> ProviderDescriptor {\n        #if os(macOS)\n        // Custom browser order that includes Chrome Beta and other variants\n        // to support users running beta/canary versions\n        let browserOrder: BrowserCookieImportOrder = [\n            .safari,\n            .chrome,\n            .chromeBeta, // Added for Chrome Beta support\n            .chromeCanary, // Added for Chrome Canary support\n            .edge,\n            .edgeBeta,\n            .brave,\n            .arc,\n            .dia,\n            .arcBeta,\n            .firefox,\n        ]\n        #else\n        let browserOrder: BrowserCookieImportOrder? = nil\n        #endif\n\n        return ProviderDescriptor(\n            id: .augment,\n            metadata: ProviderMetadata(\n                id: .augment,\n                displayName: \"Augment\",\n                sessionLabel: \"Credits\",\n                weeklyLabel: \"Usage\",\n                opusLabel: nil,\n                supportsOpus: false,\n                supportsCredits: true,\n                creditsHint: \"Augment Code credits for AI-powered coding assistance.\",\n                toggleTitle: \"Show Augment usage\",\n                cliName: \"augment\",\n                defaultEnabled: false,\n                isPrimaryProvider: false,\n                usesAccountFallback: false,\n                browserCookieOrder: browserOrder,\n                dashboardURL: \"https://app.augmentcode.com/account/subscription\",\n                statusPageURL: nil,\n                statusLinkURL: nil),\n            branding: ProviderBranding(\n                iconStyle: .augment,\n                iconResourceName: \"ProviderIcon-augment\",\n                color: ProviderColor(red: 99 / 255, green: 102 / 255, blue: 241 / 255)),\n            tokenCost: ProviderTokenCostConfig(\n                supportsTokenCost: false,\n                noDataMessage: { \"Augment cost summary is not supported.\" }),\n            fetchPlan: ProviderFetchPlan(\n                sourceModes: [.auto, .cli],\n                pipeline: ProviderFetchPipeline(resolveStrategies: { _ in\n                    var strategies: [any ProviderFetchStrategy] = []\n                    // Try CLI first (no browser prompts!)\n                    strategies.append(AugmentCLIFetchStrategy())\n                    // Fallback to web (browser cookies)\n                    strategies.append(AugmentStatusFetchStrategy())\n                    return strategies\n                })),\n            cli: ProviderCLIConfig(\n                name: \"augment\",\n                versionDetector: nil))\n    }\n}\n\nstruct AugmentCLIFetchStrategy: ProviderFetchStrategy {\n    let id: String = \"augment.cli\"\n    let kind: ProviderFetchKind = .cli\n\n    func isAvailable(_ context: ProviderFetchContext) async -> Bool {\n        // Check if auggie CLI is installed\n        let env = ProcessInfo.processInfo.environment\n        let loginPATH = LoginShellPathCache.shared.current\n        return BinaryLocator.resolveAuggieBinary(env: env, loginPATH: loginPATH) != nil\n    }\n\n    func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {\n        let probe = AuggieCLIProbe()\n        let snap = try await probe.fetch()\n        return self.makeResult(\n            usage: snap.toUsageSnapshot(),\n            sourceLabel: \"cli\")\n    }\n\n    func shouldFallback(on error: Error, context _: ProviderFetchContext) -> Bool {\n        // Fallback to web if CLI fails (not authenticated, etc.)\n        if let cliError = error as? AuggieCLIError {\n            switch cliError {\n            case .notAuthenticated, .noOutput:\n                return true\n            case .parseError:\n                return false // Don't fallback on parse errors - something is wrong\n            }\n        }\n        return true\n    }\n}\n\nstruct AugmentStatusFetchStrategy: ProviderFetchStrategy {\n    let id: String = \"augment.web\"\n    let kind: ProviderFetchKind = .web\n\n    func isAvailable(_ context: ProviderFetchContext) async -> Bool {\n        guard context.settings?.augment?.cookieSource != .off else { return false }\n        return true\n    }\n\n    func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {\n        let probe = AugmentStatusProbe()\n        let manual = Self.manualCookieHeader(from: context)\n        let logger: ((String) -> Void)? = context.verbose\n            ? { msg in CodexBarLog.logger(LogCategories.augment).verbose(msg) }\n            : nil\n        let snap = try await probe.fetch(cookieHeaderOverride: manual, logger: logger)\n        return self.makeResult(\n            usage: snap.toUsageSnapshot(),\n            sourceLabel: \"web\")\n    }\n\n    func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool {\n        false\n    }\n\n    private static func manualCookieHeader(from context: ProviderFetchContext) -> String? {\n        guard context.settings?.augment?.cookieSource == .manual else { return nil }\n        return CookieHeaderNormalizer.normalize(context.settings?.augment?.manualCookieHeader)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Augment/AugmentSessionKeepalive.swift",
    "content": "import Foundation\n\n#if os(macOS)\nimport AppKit\nimport UserNotifications\n\n/// Manages automatic session keepalive for Augment to prevent cookie expiration.\n///\n/// This actor monitors cookie expiration and proactively refreshes the session\n/// before cookies expire, ensuring uninterrupted access to Augment APIs.\n@MainActor\npublic final class AugmentSessionKeepalive {\n    // MARK: - Configuration\n\n    /// How often to check if session needs refresh (default: 1 minute for faster recovery)\n    private let checkInterval: TimeInterval = 60\n\n    /// Refresh session this many seconds before cookie expiration (default: 5 minutes)\n    private let refreshBufferSeconds: TimeInterval = 300\n\n    /// Minimum time between refresh attempts (default: 1 minute for faster recovery)\n    private let minRefreshInterval: TimeInterval = 60\n\n    /// Maximum time to wait for session refresh (default: 30 seconds)\n    private let refreshTimeout: TimeInterval = 30\n\n    // MARK: - State\n\n    private var timerTask: Task<Void, Never>?\n    private var lastRefreshAttempt: Date?\n    private var lastSuccessfulRefresh: Date?\n    private var isRefreshing = false\n    private let logger: ((String) -> Void)?\n    private var onSessionRecovered: (() async -> Void)?\n\n    /// Track consecutive failures to stop retrying after too many failures\n    private var consecutiveFailures = 0\n    private let maxConsecutiveFailures = 3 // Stop after 3 failures\n    private var hasGivenUp = false\n\n    // MARK: - Initialization\n\n    public init(logger: ((String) -> Void)? = nil, onSessionRecovered: (() async -> Void)? = nil) {\n        self.logger = logger\n        self.onSessionRecovered = onSessionRecovered\n    }\n\n    deinit {\n        self.timerTask?.cancel()\n    }\n\n    // MARK: - Public API\n\n    /// Start the automatic session keepalive timer\n    public func start() {\n        guard self.timerTask == nil else {\n            self.log(\"Keepalive already running\")\n            return\n        }\n\n        self.log(\"🚀 Starting Augment session keepalive\")\n        self.log(\"   - Check interval: \\(Int(self.checkInterval))s (every 5 minutes)\")\n        self.log(\"   - Refresh buffer: \\(Int(self.refreshBufferSeconds))s (5 minutes before expiry)\")\n        self.log(\"   - Min refresh interval: \\(Int(self.minRefreshInterval))s (2 minutes)\")\n\n        self.timerTask = Task.detached(priority: .utility) { [weak self] in\n            while !Task.isCancelled {\n                try? await Task.sleep(for: .seconds(self?.checkInterval ?? 300))\n                await self?.checkAndRefreshIfNeeded()\n            }\n        }\n\n        self.log(\"✅ Keepalive timer started successfully\")\n    }\n\n    /// Stop the automatic session keepalive timer\n    public func stop() {\n        self.log(\"Stopping Augment session keepalive\")\n        self.timerTask?.cancel()\n        self.timerTask = nil\n    }\n\n    /// Manually trigger a session refresh (bypasses rate limiting)\n    public func forceRefresh() async {\n        self.log(\"Force refresh requested\")\n        await self.performRefresh(forced: true)\n    }\n\n    // MARK: - Private Implementation\n\n    private func checkAndRefreshIfNeeded() async {\n        guard !self.isRefreshing else {\n            self.log(\"Refresh already in progress, skipping check\")\n            return\n        }\n\n        // Stop trying if we've given up\n        if self.hasGivenUp {\n            self.log(\"⏸️ Keepalive has given up after \\(self.maxConsecutiveFailures) consecutive failures\")\n            self.log(\"   User must manually log in to Augment and click 'Refresh Session'\")\n            return\n        }\n\n        // Rate limit: don't refresh too frequently\n        if let lastAttempt = self.lastRefreshAttempt {\n            let timeSinceLastAttempt = Date().timeIntervalSince(lastAttempt)\n            if timeSinceLastAttempt < self.minRefreshInterval {\n                self.log(\n                    \"Skipping refresh (last attempt \\(Int(timeSinceLastAttempt))s ago, \" +\n                        \"min interval: \\(Int(self.minRefreshInterval))s)\")\n                return\n            }\n        }\n\n        // Check if cookies are about to expire\n        let shouldRefresh = await self.shouldRefreshSession()\n        if shouldRefresh {\n            await self.performRefresh(forced: false)\n        }\n    }\n\n    private func shouldRefreshSession() async -> Bool {\n        do {\n            let session = try AugmentCookieImporter.importSession(logger: self.logger)\n\n            self.log(\"📊 Cookie Status Check:\")\n            self.log(\"   Total cookies: \\(session.cookies.count)\")\n            self.log(\"   Source: \\(session.sourceLabel)\")\n\n            // Log each cookie's expiration status\n            for cookie in session.cookies {\n                if let expiry = cookie.expiresDate {\n                    let timeUntil = expiry.timeIntervalSinceNow\n                    let status = timeUntil > 0 ? \"expires in \\(Int(timeUntil))s\" : \"EXPIRED \\(Int(-timeUntil))s ago\"\n                    self.log(\"   - \\(cookie.name): \\(status)\")\n                } else {\n                    self.log(\"   - \\(cookie.name): session cookie (no expiry)\")\n                }\n            }\n\n            // Find the earliest expiration date among session cookies\n            let expirationDates = session.cookies.compactMap(\\.expiresDate)\n\n            guard !expirationDates.isEmpty else {\n                // Session cookies (no expiration) - refresh periodically\n                self.log(\"   All cookies are session cookies (no expiration dates)\")\n                if let lastRefresh = self.lastSuccessfulRefresh {\n                    let timeSinceRefresh = Date().timeIntervalSince(lastRefresh)\n                    // Refresh every 30 minutes for session cookies\n                    if timeSinceRefresh > 1800 {\n                        self.log(\"   ⚠️ Need periodic refresh (\\(Int(timeSinceRefresh))s since last refresh)\")\n                        return true\n                    } else {\n                        self.log(\"   ✅ Recently refreshed (\\(Int(timeSinceRefresh))s ago)\")\n                        return false\n                    }\n                } else {\n                    // Never refreshed - do it now\n                    self.log(\"   ⚠️ Never refreshed - doing initial refresh\")\n                    return true\n                }\n            }\n\n            let earliestExpiration = expirationDates.min()!\n            let timeUntilExpiration = earliestExpiration.timeIntervalSinceNow\n            let expiringCookie = session.cookies.first { $0.expiresDate == earliestExpiration }\n\n            if timeUntilExpiration < self.refreshBufferSeconds {\n                self.log(\"   ⚠️ REFRESH NEEDED:\")\n                self.log(\"      Earliest expiring cookie: \\(expiringCookie?.name ?? \"unknown\")\")\n                self.log(\"      Time until expiration: \\(Int(timeUntilExpiration))s\")\n                self.log(\"      Refresh threshold: \\(Int(self.refreshBufferSeconds))s\")\n                return true\n            } else {\n                self.log(\"   ✅ Session healthy:\")\n                self.log(\"      Earliest expiring cookie: \\(expiringCookie?.name ?? \"unknown\")\")\n                self.log(\"      Time until expiration: \\(Int(timeUntilExpiration))s\")\n                return false\n            }\n        } catch {\n            self.log(\"✗ Failed to check session: \\(error.localizedDescription)\")\n            return false\n        }\n    }\n\n    private func performRefresh(forced: Bool) async {\n        self.isRefreshing = true\n        self.lastRefreshAttempt = Date()\n        defer { self.isRefreshing = false }\n\n        self.log(forced ? \"Performing forced session refresh...\" : \"Performing automatic session refresh...\")\n\n        // If this is a forced refresh (user clicked \"Refresh Session\"), reset failure tracking\n        if forced {\n            self.consecutiveFailures = 0\n            self.hasGivenUp = false\n            self.log(\"🔄 Manual refresh - resetting failure tracking\")\n        }\n\n        do {\n            // Step 1: Ping the session endpoint to trigger cookie refresh\n            let refreshed = try await self.pingSessionEndpoint()\n\n            if refreshed {\n                // Step 2: Re-import cookies from browser\n                try await Task.sleep(for: .seconds(1)) // Brief delay for browser to update cookies\n                let newSession = try AugmentCookieImporter.importSession(logger: self.logger)\n\n                self.log(\n                    \"✅ Session refresh successful - imported \\(newSession.cookies.count) cookies \" +\n                        \"from \\(newSession.sourceLabel)\")\n                self.lastSuccessfulRefresh = Date()\n\n                // Reset failure tracking on success\n                self.consecutiveFailures = 0\n                self.hasGivenUp = false\n            } else {\n                self.log(\"⚠️ Session refresh returned no new cookies\")\n                self.consecutiveFailures += 1\n                self.checkIfShouldGiveUp()\n            }\n        } catch AugmentSessionKeepaliveError.sessionExpired {\n            self.log(\"🔐 Session expired - attempting automatic recovery...\")\n            self.consecutiveFailures += 1\n\n            if self.consecutiveFailures >= self.maxConsecutiveFailures {\n                self.log(\"❌ Too many consecutive failures (\\(self.consecutiveFailures)) - giving up\")\n                self.log(\"   User must manually log in to Augment and click 'Refresh Session'\")\n                self.hasGivenUp = true\n                self.notifyUserLoginRequired()\n            } else {\n                await self.attemptSessionRecovery()\n            }\n        } catch {\n            self.log(\"✗ Session refresh failed: \\(error.localizedDescription)\")\n            self.consecutiveFailures += 1\n            self.checkIfShouldGiveUp()\n        }\n    }\n\n    private func checkIfShouldGiveUp() {\n        if self.consecutiveFailures >= self.maxConsecutiveFailures {\n            self.log(\"❌ Too many consecutive failures (\\(self.consecutiveFailures)) - giving up\")\n            self.log(\"   User must manually log in to Augment and click 'Refresh Session'\")\n            self.hasGivenUp = true\n            self.notifyUserLoginRequired()\n        }\n    }\n\n    /// Attempt to recover from an expired session by triggering browser re-authentication\n    private func attemptSessionRecovery() async {\n        self.log(\"🔄 Attempting automatic session recovery...\")\n        self.log(\"   Strategy: Open Augment dashboard to trigger browser re-auth\")\n\n        #if os(macOS)\n        // Open the Augment dashboard in the default browser\n        // This will trigger the browser to re-authenticate if the user is still logged in\n        if let url = URL(string: \"https://app.augmentcode.com\") {\n            _ = await MainActor.run {\n                NSWorkspace.shared.open(url)\n            }\n            self.log(\"   ✅ Opened Augment dashboard in browser\")\n            self.log(\"   ⏳ Waiting 5 seconds for browser to re-authenticate...\")\n\n            // Wait for browser to potentially re-authenticate\n            try? await Task.sleep(for: .seconds(5))\n\n            // Try to import cookies again\n            do {\n                let newSession = try AugmentCookieImporter.importSession(logger: self.logger)\n                self.log(\"   ✅ Session recovery successful - imported \\(newSession.cookies.count) cookies\")\n                self.lastSuccessfulRefresh = Date()\n\n                // Verify the session is actually valid by pinging the API\n                let isValid = try await self.pingSessionEndpoint()\n                if isValid {\n                    self.log(\"   ✅ Session verified - recovery complete!\")\n                    // Notify UsageStore to refresh Augment usage\n                    if let callback = self.onSessionRecovered {\n                        self.log(\"   🔄 Triggering usage refresh after successful recovery\")\n                        await callback()\n                    }\n                } else {\n                    self.log(\"   ⚠️ Session imported but not yet valid - may need manual login\")\n                    self.notifyUserLoginRequired()\n                }\n            } catch {\n                self.log(\"   ✗ Session recovery failed: \\(error.localizedDescription)\")\n                self.log(\"   ℹ️ User needs to manually log in to Augment\")\n                self.notifyUserLoginRequired()\n            }\n        }\n        #else\n        self.log(\"   ✗ Automatic recovery not supported on this platform\")\n        #endif\n    }\n\n    /// Notify the user that they need to log in to Augment\n    private func notifyUserLoginRequired() {\n        #if os(macOS)\n        self.log(\"📢 Sending notification: Augment session expired\")\n\n        Task {\n            let center = UNUserNotificationCenter.current()\n\n            // Request authorization if needed\n            do {\n                let granted = try await center.requestAuthorization(options: [.alert, .sound])\n                guard granted else {\n                    self.log(\"⚠️ Notification permission denied\")\n                    return\n                }\n            } catch {\n                self.log(\"✗ Failed to request notification permission: \\(error)\")\n                return\n            }\n\n            // Create notification content\n            let content = UNMutableNotificationContent()\n            content.title = \"Augment Session Expired\"\n            content.body = \"Please log in to app.augmentcode.com to restore your session.\"\n            content.sound = .default\n\n            // Create trigger (deliver immediately)\n            let request = UNNotificationRequest(\n                identifier: \"augment-session-expired-\\(UUID().uuidString)\",\n                content: content,\n                trigger: nil)\n\n            // Deliver notification\n            do {\n                try await center.add(request)\n                self.log(\"✅ Notification delivered successfully\")\n            } catch {\n                self.log(\"✗ Failed to deliver notification: \\(error)\")\n            }\n        }\n        #endif\n    }\n\n    /// Ping Augment's session endpoint to trigger cookie refresh\n    private func pingSessionEndpoint() async throws -> Bool {\n        // Try to get current cookies first\n        let currentSession = try? AugmentCookieImporter.importSession(logger: self.logger)\n        guard let cookieHeader = currentSession?.cookieHeader else {\n            self.log(\"No cookies available for session ping\")\n            return false\n        }\n\n        self.log(\"🔄 Attempting session refresh...\")\n        self.log(\"   Cookies being sent: \\(cookieHeader.prefix(100))...\")\n\n        // Try multiple endpoints - Augment might use different auth patterns\n        let endpoints = [\n            \"https://app.augmentcode.com/api/auth/session\", // NextAuth pattern\n            \"https://app.augmentcode.com/api/session\", // Alternative\n            \"https://app.augmentcode.com/api/user\", // User endpoint\n        ]\n\n        var receivedUnauthorized = false\n\n        for (index, urlString) in endpoints.enumerated() {\n            self.log(\"   Trying endpoint \\(index + 1)/\\(endpoints.count): \\(urlString)\")\n\n            guard let sessionURL = URL(string: urlString) else { continue }\n            var request = URLRequest(url: sessionURL)\n            request.timeoutInterval = self.refreshTimeout\n            request.setValue(cookieHeader, forHTTPHeaderField: \"Cookie\")\n            request.setValue(\"application/json\", forHTTPHeaderField: \"Accept\")\n            request.setValue(\"https://app.augmentcode.com\", forHTTPHeaderField: \"Origin\")\n            request.setValue(\"https://app.augmentcode.com\", forHTTPHeaderField: \"Referer\")\n\n            do {\n                let (data, response) = try await URLSession.shared.data(for: request)\n\n                guard let httpResponse = response as? HTTPURLResponse else {\n                    self.log(\"   ✗ Invalid response type\")\n                    continue\n                }\n\n                self.log(\"   Response: HTTP \\(httpResponse.statusCode)\")\n\n                // Log Set-Cookie headers if present\n                if let setCookies = httpResponse.allHeaderFields[\"Set-Cookie\"] as? String {\n                    self.log(\"   Set-Cookie headers received: \\(setCookies.prefix(100))...\")\n                }\n\n                if httpResponse.statusCode == 200 {\n                    // Check if we got a valid session response\n                    if let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {\n                        self.log(\"   JSON response keys: \\(json.keys.joined(separator: \", \"))\")\n\n                        if json[\"user\"] != nil || json[\"email\"] != nil || json[\"session\"] != nil {\n                            self.log(\"   ✅ Valid session data found!\")\n                            return true\n                        } else {\n                            self.log(\"   ⚠️ 200 OK but no session data in response\")\n                            // Try next endpoint\n                            continue\n                        }\n                    } else {\n                        self.log(\"   ⚠️ 200 OK but response is not JSON\")\n                        if let responseText = String(data: data, encoding: .utf8) {\n                            self.log(\"   Response text: \\(responseText.prefix(200))...\")\n                        }\n                        continue\n                    }\n                } else if httpResponse.statusCode == 401 {\n                    self.log(\"   ✗ 401 Unauthorized - session expired\")\n                    receivedUnauthorized = true\n                    // Don't throw immediately - try all endpoints first\n                    continue\n                } else if httpResponse.statusCode == 404 {\n                    self.log(\"   ✗ 404 Not Found - trying next endpoint\")\n                    continue\n                } else {\n                    self.log(\"   ✗ HTTP \\(httpResponse.statusCode) - trying next endpoint\")\n                    continue\n                }\n            } catch {\n                self.log(\"   ✗ Request failed: \\(error.localizedDescription)\")\n                continue\n            }\n        }\n\n        // If we got 401 from all endpoints, the session is definitely expired\n        if receivedUnauthorized {\n            self.log(\"⚠️ All endpoints returned 401 - session is expired\")\n            throw AugmentSessionKeepaliveError.sessionExpired\n        }\n\n        self.log(\"⚠️ All session endpoints failed or returned no valid data\")\n        return false\n    }\n\n    private static let log = CodexBarLog.logger(LogCategories.augmentKeepalive)\n\n    private func log(_ message: String) {\n        let timestamp = Date().formatted(date: .omitted, time: .standard)\n        let fullMessage = \"[\\(timestamp)] [AugmentKeepalive] \\(message)\"\n        self.logger?(fullMessage)\n        Self.log.debug(fullMessage)\n    }\n}\n\n// MARK: - Errors\n\npublic enum AugmentSessionKeepaliveError: LocalizedError, Sendable {\n    case invalidResponse\n    case sessionExpired\n    case networkError(String)\n\n    public var errorDescription: String? {\n        switch self {\n        case .invalidResponse:\n            \"Invalid response from session endpoint\"\n        case .sessionExpired:\n            \"Session has expired\"\n        case let .networkError(message):\n            \"Network error: \\(message)\"\n        }\n    }\n}\n\n#endif\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Augment/AugmentStatusProbe.swift",
    "content": "import Foundation\nimport SweetCookieKit\n\n#if os(macOS)\n\nprivate let augmentCookieImportOrder: BrowserCookieImportOrder =\n    ProviderDefaults.metadata[.augment]?.browserCookieOrder ?? Browser.defaultImportOrder\n\n// MARK: - Augment Cookie Importer\n\n/// Imports Augment session cookies from browser cookies.\npublic enum AugmentCookieImporter {\n    private static let cookieClient = BrowserCookieClient()\n    /// Auth0 session cookies used by Augment\n    /// NOTE: This list may not be exhaustive. If authentication fails with cookies present,\n    /// check debug logs for cookie names and report them.\n    private static let sessionCookieNames: Set<String> = [\n        \"_session\", // Legacy session cookie\n        \"auth0\", // Auth0 session\n        \"auth0.is.authenticated\", // Auth0 authentication flag\n        \"a0.spajs.txs\", // Auth0 SPA transaction state\n        \"__Secure-next-auth.session-token\", // NextAuth secure session\n        \"next-auth.session-token\", // NextAuth session\n        \"__Host-authjs.csrf-token\", // AuthJS CSRF token\n        \"authjs.session-token\", // AuthJS session\n    ]\n\n    public struct SessionInfo: Sendable {\n        public let cookies: [HTTPCookie]\n        public let sourceLabel: String\n\n        public init(cookies: [HTTPCookie], sourceLabel: String) {\n            self.cookies = cookies\n            self.sourceLabel = sourceLabel\n        }\n\n        public var cookieHeader: String {\n            self.cookies.map { \"\\($0.name)=\\($0.value)\" }.joined(separator: \"; \")\n        }\n\n        /// Returns cookie header filtered for a specific target URL\n        public func cookieHeader(for url: URL) -> String {\n            guard let host = url.host else { return \"\" }\n\n            let matchingCookies = self.cookies.filter { cookie in\n                let domain = cookie.domain\n\n                // Handle wildcard domains (e.g., \".augmentcode.com\")\n                if domain.hasPrefix(\".\") {\n                    let baseDomain = String(domain.dropFirst())\n                    return host == baseDomain || host.hasSuffix(\".\\(baseDomain)\")\n                }\n\n                // Exact match or subdomain match\n                return host == domain || host.hasSuffix(\".\\(domain)\")\n            }\n\n            return matchingCookies.map { \"\\($0.name)=\\($0.value)\" }.joined(separator: \"; \")\n        }\n    }\n\n    /// Attempts to import Augment cookies using the standard browser import order.\n    public static func importSession(logger: ((String) -> Void)? = nil) throws -> SessionInfo {\n        let log: (String) -> Void = { msg in logger?(\"[augment-cookie] \\(msg)\") }\n\n        let cookieDomains = [\"augmentcode.com\", \"app.augmentcode.com\"]\n        for browserSource in augmentCookieImportOrder {\n            do {\n                let query = BrowserCookieQuery(domains: cookieDomains)\n                let sources = try Self.cookieClient.records(\n                    matching: query,\n                    in: browserSource,\n                    logger: log)\n                for source in sources where !source.records.isEmpty {\n                    let httpCookies = BrowserCookieClient.makeHTTPCookies(source.records, origin: query.origin)\n\n                    // Log all cookies found for debugging\n                    let cookieNames = httpCookies.map(\\.name).joined(separator: \", \")\n                    log(\"Found \\(httpCookies.count) cookies in \\(source.label): \\(cookieNames)\")\n\n                    // Check if we have any session cookies\n                    let matchingCookies = httpCookies.filter { Self.sessionCookieNames.contains($0.name) }\n                    if !matchingCookies.isEmpty {\n                        let matchingCookieNames = matchingCookies.map(\\.name).joined(separator: \", \")\n                        log(\"✓ Found Augment session cookies in \\(source.label): \\(matchingCookieNames)\")\n                        return SessionInfo(cookies: httpCookies, sourceLabel: source.label)\n                    } else if !httpCookies.isEmpty {\n                        // Log unrecognized cookies to help discover missing session cookie names\n                        log(\n                            \"⚠️ \\(source.label) has \\(httpCookies.count) cookies but none match known session cookies\")\n                        log(\"   Cookie names found: \\(cookieNames)\")\n                        log(\"   If you're logged into Augment, please report these cookie names\")\n                        log(\"   to help improve detection\")\n                    }\n                }\n            } catch {\n                BrowserCookieAccessGate.recordIfNeeded(error)\n                log(\"\\(browserSource.displayName) cookie import failed: \\(error.localizedDescription)\")\n            }\n        }\n\n        throw AugmentStatusProbeError.noSessionCookie\n    }\n\n    /// Check if Augment session cookies are available\n    public static func hasSession(logger: ((String) -> Void)? = nil) -> Bool {\n        do {\n            let session = try self.importSession(logger: logger)\n            return !session.cookies.isEmpty\n        } catch {\n            return false\n        }\n    }\n}\n\n// MARK: - Augment API Models\n\npublic struct AugmentCreditsResponse: Codable, Sendable {\n    public let usageUnitsRemaining: Double?\n    public let usageUnitsConsumedThisBillingCycle: Double?\n    public let usageUnitsAvailable: Double?\n    public let usageBalanceStatus: String?\n\n    /// Computed properties for compatibility with existing code\n    public var credits: Double? {\n        self.usageUnitsRemaining\n    }\n\n    public var creditsUsed: Double? {\n        self.usageUnitsConsumedThisBillingCycle\n    }\n\n    public var creditsLimit: Double? {\n        guard let remaining = self.usageUnitsRemaining,\n              let consumed = self.usageUnitsConsumedThisBillingCycle\n        else {\n            return nil\n        }\n        return remaining + consumed\n    }\n\n    private enum CodingKeys: String, CodingKey {\n        case usageUnitsRemaining\n        case usageUnitsConsumedThisBillingCycle\n        case usageUnitsAvailable\n        case usageBalanceStatus\n    }\n}\n\npublic struct AugmentSubscriptionResponse: Codable, Sendable {\n    public let planName: String?\n    public let billingPeriodEnd: String?\n    public let email: String?\n    public let organization: String?\n\n    private enum CodingKeys: String, CodingKey {\n        case planName\n        case billingPeriodEnd\n        case email\n        case organization\n    }\n}\n\n// MARK: - Augment Status Snapshot\n\npublic struct AugmentStatusSnapshot: Sendable {\n    public let creditsRemaining: Double?\n    public let creditsUsed: Double?\n    public let creditsLimit: Double?\n    public let billingCycleEnd: Date?\n    public let accountEmail: String?\n    public let accountPlan: String?\n    public let rawJSON: String?\n\n    public init(\n        creditsRemaining: Double?,\n        creditsUsed: Double?,\n        creditsLimit: Double?,\n        billingCycleEnd: Date?,\n        accountEmail: String?,\n        accountPlan: String?,\n        rawJSON: String?)\n    {\n        self.creditsRemaining = creditsRemaining\n        self.creditsUsed = creditsUsed\n        self.creditsLimit = creditsLimit\n        self.billingCycleEnd = billingCycleEnd\n        self.accountEmail = accountEmail\n        self.accountPlan = accountPlan\n        self.rawJSON = rawJSON\n    }\n\n    public func toUsageSnapshot() -> UsageSnapshot {\n        let percentUsed: Double = if let used = self.creditsUsed, let limit = self.creditsLimit, limit > 0 {\n            (used / limit) * 100.0\n        } else if let remaining = self.creditsRemaining, let limit = self.creditsLimit, limit > 0 {\n            ((limit - remaining) / limit) * 100.0\n        } else {\n            0\n        }\n\n        let primary = RateWindow(\n            usedPercent: percentUsed,\n            windowMinutes: nil,\n            resetsAt: self.billingCycleEnd,\n            resetDescription: self.billingCycleEnd.map { \"Resets \\(Self.formatResetDate($0))\" })\n\n        let identity = ProviderIdentitySnapshot(\n            providerID: .augment,\n            accountEmail: self.accountEmail,\n            accountOrganization: nil,\n            loginMethod: self.accountPlan)\n\n        return UsageSnapshot(\n            primary: primary,\n            secondary: nil,\n            tertiary: nil,\n            providerCost: nil,\n            updatedAt: Date(),\n            identity: identity)\n    }\n\n    private static func formatResetDate(_ date: Date) -> String {\n        let formatter = RelativeDateTimeFormatter()\n        formatter.unitsStyle = .full\n        return formatter.localizedString(for: date, relativeTo: Date())\n    }\n}\n\n// MARK: - Augment Status Probe Error\n\npublic enum AugmentStatusProbeError: LocalizedError, Sendable {\n    case notLoggedIn\n    case networkError(String)\n    case parseFailed(String)\n    case noSessionCookie\n    case sessionExpired\n\n    public var errorDescription: String? {\n        switch self {\n        case .notLoggedIn:\n            \"Not logged in to Augment. Please log in via the CodexBar menu.\"\n        case let .networkError(msg):\n            \"Augment API error: \\(msg)\"\n        case let .parseFailed(msg):\n            \"Could not parse Augment usage: \\(msg)\"\n        case .noSessionCookie:\n            \"No Augment session found. Please log in to app.augmentcode.com in \\(augmentCookieImportOrder.loginHint).\"\n        case .sessionExpired:\n            \"Augment session expired. Please log in again.\"\n        }\n    }\n}\n\n// MARK: - Augment Session Store\n\npublic actor AugmentSessionStore {\n    public static let shared = AugmentSessionStore()\n\n    private var sessionCookies: [HTTPCookie] = []\n    private var hasLoadedFromDisk = false\n    private let fileURL: URL\n\n    private init() {\n        let fm = FileManager.default\n        let appSupport = fm.urls(for: .applicationSupportDirectory, in: .userDomainMask).first\n            ?? fm.temporaryDirectory\n        let dir = appSupport.appendingPathComponent(\"CodexBar\", isDirectory: true)\n        try? fm.createDirectory(at: dir, withIntermediateDirectories: true)\n        self.fileURL = dir.appendingPathComponent(\"augment-session.json\")\n\n        // Load saved cookies on init\n        Task { await self.loadFromDiskIfNeeded() }\n    }\n\n    public func setCookies(_ cookies: [HTTPCookie]) {\n        self.hasLoadedFromDisk = true\n        self.sessionCookies = cookies\n        self.saveToDisk()\n    }\n\n    public func getCookies() -> [HTTPCookie] {\n        self.loadFromDiskIfNeeded()\n        return self.sessionCookies\n    }\n\n    public func clearCookies() {\n        self.hasLoadedFromDisk = true\n        self.sessionCookies = []\n        try? FileManager.default.removeItem(at: self.fileURL)\n    }\n\n    public func hasValidSession() -> Bool {\n        self.loadFromDiskIfNeeded()\n        return !self.sessionCookies.isEmpty\n    }\n\n    #if DEBUG\n    func resetForTesting(clearDisk: Bool = true) {\n        self.hasLoadedFromDisk = false\n        self.sessionCookies = []\n        if clearDisk {\n            try? FileManager.default.removeItem(at: self.fileURL)\n        }\n    }\n    #endif\n\n    private func loadFromDiskIfNeeded() {\n        guard !self.hasLoadedFromDisk else { return }\n        self.hasLoadedFromDisk = true\n        self.loadFromDisk()\n    }\n\n    private func saveToDisk() {\n        // Convert cookie properties to JSON-serializable format\n        // Date values must be converted to TimeInterval (Double)\n        let cookieData = self.sessionCookies.compactMap { cookie -> [String: Any]? in\n            guard let props = cookie.properties else { return nil }\n            var serializable: [String: Any] = [:]\n            for (key, value) in props {\n                let keyString = key.rawValue\n                if let date = value as? Date {\n                    // Convert Date to TimeInterval for JSON compatibility\n                    serializable[keyString] = date.timeIntervalSince1970\n                    serializable[keyString + \"_isDate\"] = true\n                } else if let url = value as? URL {\n                    serializable[keyString] = url.absoluteString\n                    serializable[keyString + \"_isURL\"] = true\n                } else if JSONSerialization.isValidJSONObject([value]) ||\n                    value is String ||\n                    value is Bool ||\n                    value is NSNumber\n                {\n                    serializable[keyString] = value\n                }\n            }\n            return serializable\n        }\n        guard !cookieData.isEmpty,\n              let data = try? JSONSerialization.data(withJSONObject: cookieData, options: [.prettyPrinted])\n        else {\n            return\n        }\n        try? data.write(to: self.fileURL)\n    }\n\n    private func loadFromDisk() {\n        guard let data = try? Data(contentsOf: self.fileURL),\n              let cookieArray = try? JSONSerialization.jsonObject(with: data) as? [[String: Any]]\n        else { return }\n\n        self.sessionCookies = cookieArray.compactMap { props in\n            // Convert back to HTTPCookiePropertyKey dictionary\n            var cookieProps: [HTTPCookiePropertyKey: Any] = [:]\n            for (key, value) in props {\n                // Skip marker keys\n                if key.hasSuffix(\"_isDate\") || key.hasSuffix(\"_isURL\") { continue }\n\n                let propKey = HTTPCookiePropertyKey(key)\n\n                // Check if this was a Date\n                if props[key + \"_isDate\"] as? Bool == true, let interval = value as? TimeInterval {\n                    cookieProps[propKey] = Date(timeIntervalSince1970: interval)\n                }\n                // Check if this was a URL\n                else if props[key + \"_isURL\"] as? Bool == true, let urlString = value as? String {\n                    cookieProps[propKey] = URL(string: urlString)\n                } else {\n                    cookieProps[propKey] = value\n                }\n            }\n            return HTTPCookie(properties: cookieProps)\n        }\n    }\n}\n\n// MARK: - Augment Status Probe\n\npublic struct AugmentStatusProbe: Sendable {\n    public let baseURL: URL\n    public var timeout: TimeInterval = 15.0\n\n    public init(baseURL: URL = URL(string: \"https://app.augmentcode.com\")!, timeout: TimeInterval = 15.0) {\n        self.baseURL = baseURL\n        self.timeout = timeout\n    }\n\n    /// Fetch Augment usage with manual cookie header (for debugging).\n    public func fetchWithManualCookies(_ cookieHeader: String) async throws -> AugmentStatusSnapshot {\n        try await self.fetchWithCookieHeader(cookieHeader)\n    }\n\n    /// Fetch Augment usage using browser cookies with fallback to stored session.\n    public func fetch(cookieHeaderOverride: String? = nil, logger: ((String) -> Void)? = nil)\n        async throws -> AugmentStatusSnapshot\n    {\n        let log: (String) -> Void = { msg in logger?(\"[augment] \\(msg)\") }\n\n        if let override = CookieHeaderNormalizer.normalize(cookieHeaderOverride) {\n            log(\"Using manual cookie header\")\n            return try await self.fetchWithCookieHeader(override)\n        }\n\n        if let cached = CookieHeaderCache.load(provider: .augment),\n           !cached.cookieHeader.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty\n        {\n            log(\"Using cached cookie header from \\(cached.sourceLabel)\")\n            do {\n                return try await self.fetchWithCookieHeader(cached.cookieHeader)\n            } catch let error as AugmentStatusProbeError {\n                switch error {\n                case .notLoggedIn, .sessionExpired:\n                    CookieHeaderCache.clear(provider: .augment)\n                default:\n                    throw error\n                }\n            } catch {\n                throw error\n            }\n        }\n\n        // Try importing cookies from the configured browser order first.\n        do {\n            let session = try AugmentCookieImporter.importSession(logger: log)\n            log(\"Using cookies from \\(session.sourceLabel)\")\n            let snapshot = try await self.fetchWithCookieHeader(session.cookieHeader)\n\n            // SUCCESS: Save cookies to fallback store for future use\n            await AugmentSessionStore.shared.setCookies(session.cookies)\n            log(\"Saved session cookies to fallback store\")\n            CookieHeaderCache.store(\n                provider: .augment,\n                cookieHeader: session.cookieHeader,\n                sourceLabel: session.sourceLabel)\n\n            return snapshot\n        } catch {\n            BrowserCookieAccessGate.recordIfNeeded(error)\n            log(\"Browser cookie import failed: \\(error.localizedDescription)\")\n        }\n\n        // Fall back to stored session cookies (from previous successful fetch or \"Add Account\" login flow)\n        let storedCookies = await AugmentSessionStore.shared.getCookies()\n        if !storedCookies.isEmpty {\n            log(\"Using stored session cookies\")\n            let cookieHeader = storedCookies.map { \"\\($0.name)=\\($0.value)\" }.joined(separator: \"; \")\n            do {\n                return try await self.fetchWithCookieHeader(cookieHeader)\n            } catch {\n                if case AugmentStatusProbeError.notLoggedIn = error {\n                    // Clear only when auth is invalid; keep for transient failures.\n                    await AugmentSessionStore.shared.clearCookies()\n                    log(\"Stored session invalid, cleared\")\n                } else if case AugmentStatusProbeError.sessionExpired = error {\n                    await AugmentSessionStore.shared.clearCookies()\n                    log(\"Stored session expired, cleared\")\n                } else {\n                    log(\"Stored session failed: \\(error.localizedDescription)\")\n                }\n            }\n        }\n\n        throw AugmentStatusProbeError.noSessionCookie\n    }\n\n    private func fetchWithCookieHeader(_ cookieHeader: String) async throws -> AugmentStatusSnapshot {\n        // Fetch credits (required)\n        let (creditsResponse, creditsJSON) = try await self.fetchCredits(cookieHeader: cookieHeader)\n\n        // Fetch subscription (optional - provides plan name and billing cycle)\n        let subscriptionResult: (AugmentSubscriptionResponse?, String?) = await {\n            do {\n                let (response, json) = try await self.fetchSubscription(cookieHeader: cookieHeader)\n                return (response, json)\n            } catch {\n                // Subscription API is optional - don't fail the whole fetch if it's unavailable\n                return (nil, nil)\n            }\n        }()\n\n        return self.parseResponse(\n            credits: creditsResponse,\n            subscription: subscriptionResult.0,\n            creditsJSON: creditsJSON,\n            subscriptionJSON: subscriptionResult.1)\n    }\n\n    private func fetchCredits(cookieHeader: String) async throws -> (AugmentCreditsResponse, String) {\n        let url = self.baseURL.appendingPathComponent(\"/api/credits\")\n        var request = URLRequest(url: url)\n        request.timeoutInterval = self.timeout\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Accept\")\n        request.setValue(cookieHeader, forHTTPHeaderField: \"Cookie\")\n\n        let (data, response) = try await URLSession.shared.data(for: request)\n\n        guard let httpResponse = response as? HTTPURLResponse else {\n            throw AugmentStatusProbeError.networkError(\"Invalid response\")\n        }\n\n        if httpResponse.statusCode == 401 {\n            // Session expired - trigger automatic recovery\n            throw AugmentStatusProbeError.sessionExpired\n        }\n\n        if httpResponse.statusCode == 403 {\n            let responseBody = String(data: data, encoding: .utf8) ?? \"\"\n            throw AugmentStatusProbeError.networkError(\"HTTP \\(httpResponse.statusCode): \\(responseBody)\")\n        }\n\n        guard httpResponse.statusCode == 200 else {\n            let responseBody = String(data: data, encoding: .utf8) ?? \"\"\n            throw AugmentStatusProbeError.networkError(\"HTTP \\(httpResponse.statusCode): \\(responseBody)\")\n        }\n\n        let rawJSON = String(data: data, encoding: .utf8) ?? \"\"\n        let decoder = JSONDecoder()\n        do {\n            let response = try decoder.decode(AugmentCreditsResponse.self, from: data)\n            return (response, rawJSON)\n        } catch {\n            throw AugmentStatusProbeError.parseFailed(\"Credits response: \\(error.localizedDescription)\")\n        }\n    }\n\n    private func fetchSubscription(cookieHeader: String) async throws -> (AugmentSubscriptionResponse, String) {\n        let url = self.baseURL.appendingPathComponent(\"/api/subscription\")\n        var request = URLRequest(url: url)\n        request.timeoutInterval = self.timeout\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Accept\")\n        request.setValue(cookieHeader, forHTTPHeaderField: \"Cookie\")\n\n        let (data, response) = try await URLSession.shared.data(for: request)\n\n        guard let httpResponse = response as? HTTPURLResponse else {\n            throw AugmentStatusProbeError.networkError(\"Invalid response\")\n        }\n\n        if httpResponse.statusCode == 401 {\n            // Session expired - trigger automatic recovery\n            throw AugmentStatusProbeError.sessionExpired\n        }\n\n        if httpResponse.statusCode == 403 {\n            throw AugmentStatusProbeError.notLoggedIn\n        }\n\n        guard httpResponse.statusCode == 200 else {\n            throw AugmentStatusProbeError.networkError(\"HTTP \\(httpResponse.statusCode)\")\n        }\n\n        let rawJSON = String(data: data, encoding: .utf8) ?? \"\"\n        let decoder = JSONDecoder()\n        do {\n            let response = try decoder.decode(AugmentSubscriptionResponse.self, from: data)\n            return (response, rawJSON)\n        } catch {\n            throw AugmentStatusProbeError.parseFailed(\"Subscription response: \\(error.localizedDescription)\")\n        }\n    }\n\n    private func parseResponse(\n        credits: AugmentCreditsResponse,\n        subscription: AugmentSubscriptionResponse?,\n        creditsJSON: String,\n        subscriptionJSON: String?) -> AugmentStatusSnapshot\n    {\n        // Combine both API responses for debugging\n        var combinedJSON = \"Credits API:\\n\\(creditsJSON)\"\n        if let subJSON = subscriptionJSON {\n            combinedJSON += \"\\n\\nSubscription API:\\n\\(subJSON)\"\n        }\n\n        // Parse billing period end date from ISO8601 string\n        let billingCycleEnd: Date? = {\n            guard let dateString = subscription?.billingPeriodEnd else { return nil }\n            let formatter = ISO8601DateFormatter()\n            return formatter.date(from: dateString)\n        }()\n\n        return AugmentStatusSnapshot(\n            creditsRemaining: credits.credits,\n            creditsUsed: credits.creditsUsed,\n            creditsLimit: credits.creditsLimit,\n            billingCycleEnd: billingCycleEnd,\n            accountEmail: subscription?.email,\n            accountPlan: subscription?.planName,\n            rawJSON: combinedJSON)\n    }\n\n    /// Debug probe that returns raw API responses\n    public func debugRawProbe() async -> String {\n        let stamp = ISO8601DateFormatter().string(from: Date())\n        var lines: [String] = []\n        lines.append(\"=== Augment Debug Probe @ \\(stamp) ===\")\n        lines.append(\"\")\n\n        do {\n            let snapshot = try await self.fetch(logger: { msg in lines.append(\"[log] \\(msg)\") })\n            lines.append(\"\")\n            lines.append(\"Probe Success\")\n            lines.append(\"\")\n            lines.append(\"Credits Balance:\")\n            lines.append(\"  Remaining: \\(snapshot.creditsRemaining?.description ?? \"nil\")\")\n            lines.append(\"  Used: \\(snapshot.creditsUsed?.description ?? \"nil\")\")\n            lines.append(\"  Limit: \\(snapshot.creditsLimit?.description ?? \"nil\")\")\n            lines.append(\"\")\n            lines.append(\"Billing Cycle End: \\(snapshot.billingCycleEnd?.description ?? \"nil\")\")\n            lines.append(\"Account Email: \\(snapshot.accountEmail ?? \"nil\")\")\n            lines.append(\"Account Plan: \\(snapshot.accountPlan ?? \"nil\")\")\n\n            if let rawJSON = snapshot.rawJSON {\n                lines.append(\"\")\n                lines.append(\"Raw API Response:\")\n                lines.append(rawJSON)\n            }\n\n            let output = lines.joined(separator: \"\\n\")\n            Task { @MainActor in Self.recordDump(output) }\n            return output\n        } catch {\n            lines.append(\"\")\n            lines.append(\"Probe Failed: \\(error.localizedDescription)\")\n            let output = lines.joined(separator: \"\\n\")\n            Task { @MainActor in Self.recordDump(output) }\n            return output\n        }\n    }\n\n    // MARK: - Dump storage (in-memory ring buffer)\n\n    @MainActor private static var recentDumps: [String] = []\n\n    @MainActor private static func recordDump(_ text: String) {\n        if self.recentDumps.count >= 5 { self.recentDumps.removeFirst() }\n        self.recentDumps.append(text)\n    }\n\n    public static func latestDumps() async -> String {\n        await MainActor.run {\n            let result = Self.recentDumps.joined(separator: \"\\n\\n---\\n\\n\")\n            return result.isEmpty ? \"No Augment probe dumps captured yet.\" : result\n        }\n    }\n}\n\n#else\n\n// MARK: - Augment (Unsupported)\n\npublic enum AugmentStatusProbeError: LocalizedError, Sendable {\n    case notSupported\n\n    public var errorDescription: String? {\n        \"Augment is only supported on macOS.\"\n    }\n}\n\npublic struct AugmentStatusSnapshot: Sendable {\n    public init() {}\n\n    public func toUsageSnapshot() -> UsageSnapshot {\n        UsageSnapshot(\n            primary: RateWindow(usedPercent: 0, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            secondary: nil,\n            tertiary: nil,\n            providerCost: nil,\n            updatedAt: Date(),\n            identity: nil)\n    }\n}\n\npublic struct AugmentStatusProbe: Sendable {\n    public init(baseURL: URL = URL(string: \"https://app.augmentcode.com\")!, timeout: TimeInterval = 15.0) {\n        _ = baseURL\n        _ = timeout\n    }\n\n    public func fetch(cookieHeaderOverride: String? = nil, logger: ((String) -> Void)? = nil)\n        async throws -> AugmentStatusSnapshot\n    {\n        _ = cookieHeaderOverride\n        _ = logger\n        throw AugmentStatusProbeError.notSupported\n    }\n}\n\n#endif\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/CLIProbeSessionResetter.swift",
    "content": "import Foundation\n\npublic enum CLIProbeSessionResetter {\n    public static func resetAll() async {\n        await ClaudeCLISession.shared.reset()\n        await CodexCLISession.shared.reset()\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Claude/ClaudeCLISession.swift",
    "content": "#if canImport(Darwin)\nimport Darwin\n#else\nimport Glibc\n#endif\nimport Foundation\n\nactor ClaudeCLISession {\n    static let shared = ClaudeCLISession()\n    private static let log = CodexBarLog.logger(LogCategories.claudeCLI)\n\n    enum SessionError: LocalizedError {\n        case launchFailed(String)\n        case ioFailed(String)\n        case timedOut\n        case processExited\n\n        var errorDescription: String? {\n            switch self {\n            case let .launchFailed(msg): \"Failed to launch Claude CLI session: \\(msg)\"\n            case let .ioFailed(msg): \"Claude CLI PTY I/O failed: \\(msg)\"\n            case .timedOut: \"Claude CLI session timed out.\"\n            case .processExited: \"Claude CLI session exited.\"\n            }\n        }\n    }\n\n    private var process: Process?\n    private var primaryFD: Int32 = -1\n    private var primaryHandle: FileHandle?\n    private var secondaryHandle: FileHandle?\n    private var processGroup: pid_t?\n    private var binaryPath: String?\n    private var startedAt: Date?\n\n    private let promptSends: [String: String] = [\n        \"Do you trust the files in this folder?\": \"y\\r\",\n        \"Quick safety check:\": \"\\r\",\n        \"Yes, I trust this folder\": \"\\r\",\n        \"Ready to code here?\": \"\\r\",\n        \"Press Enter to continue\": \"\\r\",\n    ]\n\n    private struct RollingBuffer {\n        private let maxNeedle: Int\n        private var tail = Data()\n\n        init(maxNeedle: Int) {\n            self.maxNeedle = max(0, maxNeedle)\n        }\n\n        mutating func append(_ data: Data) -> Data {\n            guard !data.isEmpty else { return Data() }\n            var combined = Data()\n            combined.reserveCapacity(self.tail.count + data.count)\n            combined.append(self.tail)\n            combined.append(data)\n            if self.maxNeedle > 1 {\n                if combined.count >= self.maxNeedle - 1 {\n                    self.tail = combined.suffix(self.maxNeedle - 1)\n                } else {\n                    self.tail = combined\n                }\n            } else {\n                self.tail.removeAll(keepingCapacity: true)\n            }\n            return combined\n        }\n    }\n\n    private static func normalizedNeedle(_ text: String) -> String {\n        String(text.lowercased().filter { !$0.isWhitespace })\n    }\n\n    private static func commandPaletteSends(for subcommand: String) -> [String: String] {\n        let normalized = subcommand.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()\n        switch normalized {\n        case \"/usage\":\n            // Claude's command palette can render several \"Show ...\" actions together; only auto-confirm the\n            // usage-related actions here so we do not accidentally execute /status.\n            return [\n                \"Show plan\": \"\\r\",\n                \"Show plan usage limits\": \"\\r\",\n            ]\n        case \"/status\":\n            return [\n                \"Show Claude Code\": \"\\r\",\n                \"Show Claude Code status\": \"\\r\",\n            ]\n        default:\n            return [:]\n        }\n    }\n\n    func capture(\n        subcommand: String,\n        binary: String,\n        timeout: TimeInterval,\n        idleTimeout: TimeInterval? = 3.0,\n        stopOnSubstrings: [String] = [],\n        settleAfterStop: TimeInterval = 0.25,\n        sendEnterEvery: TimeInterval? = nil) async throws -> String\n    {\n        try self.ensureStarted(binary: binary)\n        if let startedAt {\n            let sinceStart = Date().timeIntervalSince(startedAt)\n            // Claude's TUI can drop early keystrokes while it's still initializing. Wait a bit longer than the\n            // original 0.4s to ensure slash commands reliably open their panels.\n            if sinceStart < 2.0 {\n                let delay = UInt64((2.0 - sinceStart) * 1_000_000_000)\n                try await Task.sleep(nanoseconds: delay)\n            }\n        }\n        self.drainOutput()\n\n        let trimmed = subcommand.trimmingCharacters(in: .whitespacesAndNewlines)\n        if !trimmed.isEmpty {\n            try self.send(trimmed)\n            try self.send(\"\\r\")\n        }\n\n        let stopNeedles = stopOnSubstrings.map { Self.normalizedNeedle($0) }\n        var sendMap = self.promptSends\n        for (needle, keys) in Self.commandPaletteSends(for: trimmed) {\n            sendMap[needle] = keys\n        }\n        let sendNeedles = sendMap.map { (needle: Self.normalizedNeedle($0.key), keys: $0.value) }\n        let cursorQuery = Data([0x1B, 0x5B, 0x36, 0x6E])\n        let needleLengths =\n            stopOnSubstrings.map(\\.utf8.count) +\n            sendMap.keys.map(\\.utf8.count) +\n            [cursorQuery.count]\n        let maxNeedle = needleLengths.max() ?? cursorQuery.count\n        var scanBuffer = RollingBuffer(maxNeedle: maxNeedle)\n        var triggeredSends = Set<String>()\n\n        var buffer = Data()\n        var scanTailText = \"\"\n        var utf8Carry = Data()\n        let deadline = Date().addingTimeInterval(timeout)\n        var lastOutputAt = Date()\n        var lastEnterAt = Date()\n        var stoppedEarly = false\n        // Only send periodic Enter when the caller explicitly asks for it (used for /usage rendering).\n        // For /status, periodic input can keep producing output and prevent idle-timeout short-circuiting.\n        let effectiveEnterEvery: TimeInterval? = sendEnterEvery\n\n        while Date() < deadline {\n            let newData = self.readChunk()\n            if !newData.isEmpty {\n                buffer.append(newData)\n                lastOutputAt = Date()\n                Self.appendScanText(newData: newData, scanTailText: &scanTailText, utf8Carry: &utf8Carry)\n                if scanTailText.count > 8192 { scanTailText = String(scanTailText.suffix(8192)) }\n            }\n\n            let scanData = scanBuffer.append(newData)\n            if !scanData.isEmpty,\n               scanData.range(of: cursorQuery) != nil\n            {\n                try? self.send(\"\\u{1b}[1;1R\")\n            }\n\n            let normalizedScan = Self.normalizedNeedle(TextParsing.stripANSICodes(scanTailText))\n\n            for item in sendNeedles where !triggeredSends.contains(item.needle) {\n                if normalizedScan.contains(item.needle) {\n                    try? self.send(item.keys)\n                    triggeredSends.insert(item.needle)\n                }\n            }\n\n            if stopNeedles.contains(where: normalizedScan.contains) {\n                stoppedEarly = true\n                break\n            }\n\n            if self.shouldStopForIdleTimeout(\n                idleTimeout: idleTimeout,\n                bufferIsEmpty: buffer.isEmpty,\n                lastOutputAt: lastOutputAt)\n            {\n                stoppedEarly = true\n                break\n            }\n\n            self.sendPeriodicEnterIfNeeded(every: effectiveEnterEvery, lastEnterAt: &lastEnterAt)\n\n            if let proc = self.process, !proc.isRunning {\n                throw SessionError.processExited\n            }\n\n            try await Task.sleep(nanoseconds: 60_000_000)\n        }\n\n        if stoppedEarly {\n            let settle = max(0, min(settleAfterStop, deadline.timeIntervalSinceNow))\n            if settle > 0 {\n                let settleDeadline = Date().addingTimeInterval(settle)\n                while Date() < settleDeadline {\n                    let newData = self.readChunk()\n                    if !newData.isEmpty { buffer.append(newData) }\n                    try await Task.sleep(nanoseconds: 50_000_000)\n                }\n            }\n        }\n\n        guard !buffer.isEmpty, let text = String(data: buffer, encoding: .utf8) else {\n            throw SessionError.timedOut\n        }\n        return text\n    }\n\n    private static func appendScanText(newData: Data, scanTailText: inout String, utf8Carry: inout Data) {\n        // PTY reads can split multibyte UTF-8 sequences. Keep a small carry buffer so prompt/stop scanning doesn't\n        // drop chunks when the decode fails due to an incomplete trailing sequence.\n        var combined = Data()\n        combined.reserveCapacity(utf8Carry.count + newData.count)\n        combined.append(utf8Carry)\n        combined.append(newData)\n\n        if let chunk = String(data: combined, encoding: .utf8) {\n            scanTailText.append(chunk)\n            utf8Carry.removeAll(keepingCapacity: true)\n            return\n        }\n\n        for trimCount in 1...3 where combined.count > trimCount {\n            let prefix = combined.dropLast(trimCount)\n            if let chunk = String(data: prefix, encoding: .utf8) {\n                scanTailText.append(chunk)\n                utf8Carry = Data(combined.suffix(trimCount))\n                return\n            }\n        }\n\n        // If the data is still not UTF-8 decodable, keep only a small suffix to avoid unbounded growth.\n        utf8Carry = Data(combined.suffix(12))\n    }\n\n    func reset() {\n        self.cleanup()\n    }\n\n    private func ensureStarted(binary: String) throws {\n        if let proc = self.process, proc.isRunning, self.binaryPath == binary {\n            Self.log.debug(\"Claude CLI session reused\")\n            return\n        }\n        self.cleanup()\n\n        var primaryFD: Int32 = -1\n        var secondaryFD: Int32 = -1\n        var win = winsize(ws_row: 50, ws_col: 160, ws_xpixel: 0, ws_ypixel: 0)\n        guard openpty(&primaryFD, &secondaryFD, nil, nil, &win) == 0 else {\n            Self.log.warning(\"Claude CLI PTY openpty failed\")\n            throw SessionError.launchFailed(\"openpty failed\")\n        }\n        _ = fcntl(primaryFD, F_SETFL, O_NONBLOCK)\n\n        let primaryHandle = FileHandle(fileDescriptor: primaryFD, closeOnDealloc: true)\n        let secondaryHandle = FileHandle(fileDescriptor: secondaryFD, closeOnDealloc: true)\n\n        let proc = Process()\n        let resolvedURL = URL(fileURLWithPath: binary)\n        let disableWatchdog = ProcessInfo.processInfo.environment[\"CODEXBAR_DISABLE_CLAUDE_WATCHDOG\"] == \"1\"\n        if !disableWatchdog,\n           resolvedURL.lastPathComponent == \"claude\",\n           let watchdog = TTYCommandRunner.locateBundledHelper(\"CodexBarClaudeWatchdog\")\n        {\n            proc.executableURL = URL(fileURLWithPath: watchdog)\n            proc.arguments = [\"--\", binary, \"--allowed-tools\", \"\"]\n        } else {\n            proc.executableURL = resolvedURL\n            proc.arguments = [\"--allowed-tools\", \"\"]\n        }\n        proc.standardInput = secondaryHandle\n        proc.standardOutput = secondaryHandle\n        proc.standardError = secondaryHandle\n\n        let workingDirectory = ClaudeStatusProbe.probeWorkingDirectoryURL()\n        proc.currentDirectoryURL = workingDirectory\n        var env = TTYCommandRunner.enrichedEnvironment()\n        env = Self.scrubbedClaudeEnvironment(from: env)\n        env[\"PWD\"] = workingDirectory.path\n        proc.environment = env\n\n        do {\n            try proc.run()\n            Self.log.debug(\n                \"Claude CLI session started\",\n                metadata: [\"binary\": URL(fileURLWithPath: binary).lastPathComponent])\n        } catch {\n            Self.log.warning(\"Claude CLI launch failed\", metadata: [\"error\": error.localizedDescription])\n            try? primaryHandle.close()\n            try? secondaryHandle.close()\n            throw SessionError.launchFailed(error.localizedDescription)\n        }\n\n        let pid = proc.processIdentifier\n        var processGroup: pid_t?\n        if setpgid(pid, pid) == 0 {\n            processGroup = pid\n        }\n\n        self.process = proc\n        self.primaryFD = primaryFD\n        self.primaryHandle = primaryHandle\n        self.secondaryHandle = secondaryHandle\n        self.processGroup = processGroup\n        self.binaryPath = binary\n        self.startedAt = Date()\n    }\n\n    private static func scrubbedClaudeEnvironment(from base: [String: String]) -> [String: String] {\n        var env = base\n        let explicitKeys: [String] = [\n            ClaudeOAuthCredentialsStore.environmentTokenKey,\n            ClaudeOAuthCredentialsStore.environmentScopesKey,\n        ]\n        for key in explicitKeys {\n            env.removeValue(forKey: key)\n        }\n        for key in env.keys where key.hasPrefix(\"ANTHROPIC_\") {\n            env.removeValue(forKey: key)\n        }\n        return env\n    }\n\n    private func cleanup() {\n        if self.process != nil {\n            Self.log.debug(\"Claude CLI session stopping\")\n        }\n        if let proc = self.process, proc.isRunning {\n            try? self.writeAllToPrimary(Data(\"/exit\\r\".utf8))\n        }\n        try? self.primaryHandle?.close()\n        try? self.secondaryHandle?.close()\n\n        if let proc = self.process, proc.isRunning {\n            proc.terminate()\n        }\n        if let pgid = self.processGroup {\n            kill(-pgid, SIGTERM)\n        }\n        let waitDeadline = Date().addingTimeInterval(1.0)\n        if let proc = self.process {\n            while proc.isRunning, Date() < waitDeadline {\n                usleep(100_000)\n            }\n            if proc.isRunning {\n                if let pgid = self.processGroup {\n                    kill(-pgid, SIGKILL)\n                }\n                kill(proc.processIdentifier, SIGKILL)\n            }\n        }\n\n        self.process = nil\n        self.primaryHandle = nil\n        self.secondaryHandle = nil\n        self.primaryFD = -1\n        self.processGroup = nil\n        self.startedAt = nil\n    }\n\n    private func readChunk() -> Data {\n        guard self.primaryFD >= 0 else { return Data() }\n        var appended = Data()\n        while true {\n            var tmp = [UInt8](repeating: 0, count: 8192)\n            let n = read(self.primaryFD, &tmp, tmp.count)\n            if n > 0 {\n                appended.append(contentsOf: tmp.prefix(n))\n                continue\n            }\n            break\n        }\n        return appended\n    }\n\n    private func drainOutput() {\n        _ = self.readChunk()\n    }\n\n    private func shouldStopForIdleTimeout(\n        idleTimeout: TimeInterval?,\n        bufferIsEmpty: Bool,\n        lastOutputAt: Date) -> Bool\n    {\n        guard let idleTimeout, !bufferIsEmpty else { return false }\n        return Date().timeIntervalSince(lastOutputAt) >= idleTimeout\n    }\n\n    private func sendPeriodicEnterIfNeeded(every: TimeInterval?, lastEnterAt: inout Date) {\n        guard let every, Date().timeIntervalSince(lastEnterAt) >= every else { return }\n        try? self.send(\"\\r\")\n        lastEnterAt = Date()\n    }\n\n    private func send(_ text: String) throws {\n        guard let data = text.data(using: .utf8) else { return }\n        guard self.primaryFD >= 0 else { throw SessionError.processExited }\n        try self.writeAllToPrimary(data)\n    }\n\n    private func writeAllToPrimary(_ data: Data) throws {\n        guard self.primaryFD >= 0 else { throw SessionError.processExited }\n        try data.withUnsafeBytes { rawBytes in\n            guard let baseAddress = rawBytes.baseAddress else { return }\n            var offset = 0\n            var retries = 0\n            while offset < rawBytes.count {\n                let written = write(self.primaryFD, baseAddress.advanced(by: offset), rawBytes.count - offset)\n                if written > 0 {\n                    offset += written\n                    retries = 0\n                    continue\n                }\n                if written == 0 { break }\n\n                let err = errno\n                if err == EINTR || err == EAGAIN || err == EWOULDBLOCK {\n                    retries += 1\n                    if retries > 200 {\n                        throw SessionError.ioFailed(\"write to PTY would block\")\n                    }\n                    usleep(5000)\n                    continue\n                }\n                throw SessionError.ioFailed(\"write to PTY failed: \\(String(cString: strerror(err)))\")\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Claude/ClaudeCredentialRouting.swift",
    "content": "import Foundation\n\npublic enum ClaudeCredentialRouting: Sendable, Equatable {\n    case none\n    case oauth(accessToken: String)\n    case webCookie(header: String)\n\n    public static func resolve(tokenAccountToken: String?, manualCookieHeader: String?) -> Self {\n        if let tokenAccountToken,\n           let route = self.resolvePrimaryCredential(tokenAccountToken)\n        {\n            return route\n        }\n\n        guard let manualCookieHeader = self.normalizedWebCookie(manualCookieHeader) else {\n            return .none\n        }\n        return .webCookie(header: manualCookieHeader)\n    }\n\n    public var oauthAccessToken: String? {\n        guard case let .oauth(accessToken) = self else { return nil }\n        return accessToken\n    }\n\n    public var manualCookieHeader: String? {\n        guard case let .webCookie(header) = self else { return nil }\n        return header\n    }\n\n    public var isOAuth: Bool {\n        if case .oauth = self { return true }\n        return false\n    }\n\n    private static func resolvePrimaryCredential(_ raw: String) -> Self? {\n        if let accessToken = self.normalizedOAuthToken(raw) {\n            return .oauth(accessToken: accessToken)\n        }\n        if let cookieHeader = self.normalizedWebCookie(raw) {\n            return .webCookie(header: cookieHeader)\n        }\n        return nil\n    }\n\n    private static func normalizedOAuthToken(_ raw: String?) -> String? {\n        guard let trimmed = self.cleaned(raw) else { return nil }\n        let lower = trimmed.lowercased()\n        if lower.contains(\"cookie:\") || trimmed.contains(\"=\") {\n            return nil\n        }\n        if lower.hasPrefix(\"bearer \") {\n            let bearerTrimmed = trimmed.dropFirst(\"bearer \".count)\n                .trimmingCharacters(in: .whitespacesAndNewlines)\n            guard !bearerTrimmed.isEmpty else { return nil }\n            let lowerBearerTrimmed = bearerTrimmed.lowercased()\n            return lowerBearerTrimmed.hasPrefix(\"sk-ant-oat\") ? bearerTrimmed : nil\n        }\n        return lower.hasPrefix(\"sk-ant-oat\") ? trimmed : nil\n    }\n\n    private static func normalizedWebCookie(_ raw: String?) -> String? {\n        guard let normalized = CookieHeaderNormalizer.normalize(raw) else { return nil }\n        if normalized.contains(\"=\") {\n            return normalized\n        }\n        return \"sessionKey=\\(normalized)\"\n    }\n\n    private static func cleaned(_ raw: String?) -> String? {\n        guard let trimmed = raw?.trimmingCharacters(in: .whitespacesAndNewlines),\n              !trimmed.isEmpty\n        else {\n            return nil\n        }\n        return trimmed\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthCredentialModels.swift",
    "content": "import Foundation\n\n#if os(macOS)\nimport Security\n#endif\n\npublic struct ClaudeOAuthCredentials: Sendable {\n    public let accessToken: String\n    public let refreshToken: String?\n    public let expiresAt: Date?\n    public let scopes: [String]\n    public let rateLimitTier: String?\n\n    public init(\n        accessToken: String,\n        refreshToken: String?,\n        expiresAt: Date?,\n        scopes: [String],\n        rateLimitTier: String?)\n    {\n        self.accessToken = accessToken\n        self.refreshToken = refreshToken\n        self.expiresAt = expiresAt\n        self.scopes = scopes\n        self.rateLimitTier = rateLimitTier\n    }\n\n    public var isExpired: Bool {\n        guard let expiresAt else { return true }\n        return Date() >= expiresAt\n    }\n\n    public var expiresIn: TimeInterval? {\n        guard let expiresAt else { return nil }\n        return expiresAt.timeIntervalSinceNow\n    }\n\n    public static func parse(data: Data) throws -> ClaudeOAuthCredentials {\n        let decoder = JSONDecoder()\n        guard let root = try? decoder.decode(Root.self, from: data) else {\n            throw ClaudeOAuthCredentialsError.decodeFailed\n        }\n        guard let oauth = root.claudeAiOauth else {\n            throw ClaudeOAuthCredentialsError.missingOAuth\n        }\n        let accessToken = oauth.accessToken?.trimmingCharacters(in: .whitespacesAndNewlines) ?? \"\"\n        guard !accessToken.isEmpty else {\n            throw ClaudeOAuthCredentialsError.missingAccessToken\n        }\n        let expiresAt = oauth.expiresAt.map { millis in\n            Date(timeIntervalSince1970: millis / 1000.0)\n        }\n        return ClaudeOAuthCredentials(\n            accessToken: accessToken,\n            refreshToken: oauth.refreshToken,\n            expiresAt: expiresAt,\n            scopes: oauth.scopes ?? [],\n            rateLimitTier: oauth.rateLimitTier)\n    }\n\n    private struct Root: Decodable {\n        let claudeAiOauth: OAuth?\n    }\n\n    private struct OAuth: Decodable {\n        let accessToken: String?\n        let refreshToken: String?\n        let expiresAt: Double?\n        let scopes: [String]?\n        let rateLimitTier: String?\n\n        enum CodingKeys: String, CodingKey {\n            case accessToken\n            case refreshToken\n            case expiresAt\n            case scopes\n            case rateLimitTier\n        }\n    }\n}\n\nextension ClaudeOAuthCredentials {\n    func diagnosticsMetadata(now: Date = Date()) -> [String: String] {\n        let hasRefreshToken = !(self.refreshToken?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ?? true)\n        let hasUserProfileScope = self.scopes.contains(\"user:profile\")\n\n        var metadata: [String: String] = [\n            \"hasRefreshToken\": \"\\(hasRefreshToken)\",\n            \"scopesCount\": \"\\(self.scopes.count)\",\n            \"hasUserProfileScope\": \"\\(hasUserProfileScope)\",\n        ]\n\n        if let expiresAt = self.expiresAt {\n            let expiresAtMs = Int(expiresAt.timeIntervalSince1970 * 1000.0)\n            let expiresInSec = Int(expiresAt.timeIntervalSince(now).rounded())\n            metadata[\"expiresAtMs\"] = \"\\(expiresAtMs)\"\n            metadata[\"expiresInSec\"] = \"\\(expiresInSec)\"\n            metadata[\"isExpired\"] = \"\\(now >= expiresAt)\"\n        } else {\n            metadata[\"expiresAtMs\"] = \"nil\"\n            metadata[\"expiresInSec\"] = \"nil\"\n            metadata[\"isExpired\"] = \"true\"\n        }\n\n        return metadata\n    }\n}\n\npublic enum ClaudeOAuthCredentialOwner: String, Codable, Sendable {\n    case claudeCLI\n    case codexbar\n    case environment\n}\n\npublic enum ClaudeOAuthCredentialSource: String, Sendable {\n    case environment\n    case memoryCache\n    case cacheKeychain\n    case credentialsFile\n    case claudeKeychain\n}\n\npublic struct ClaudeOAuthCredentialRecord: Sendable {\n    public let credentials: ClaudeOAuthCredentials\n    public let owner: ClaudeOAuthCredentialOwner\n    public let source: ClaudeOAuthCredentialSource\n\n    public init(\n        credentials: ClaudeOAuthCredentials,\n        owner: ClaudeOAuthCredentialOwner,\n        source: ClaudeOAuthCredentialSource)\n    {\n        self.credentials = credentials\n        self.owner = owner\n        self.source = source\n    }\n}\n\npublic enum ClaudeOAuthCredentialsError: LocalizedError, Sendable {\n    case decodeFailed\n    case missingOAuth\n    case missingAccessToken\n    case notFound\n    case keychainError(Int)\n    case readFailed(String)\n    case refreshFailed(String)\n    case noRefreshToken\n    case refreshDelegatedToClaudeCLI\n\n    public var errorDescription: String? {\n        switch self {\n        case .decodeFailed:\n            return \"Claude OAuth credentials are invalid.\"\n        case .missingOAuth:\n            return \"Claude OAuth credentials missing. Run `claude` to authenticate.\"\n        case .missingAccessToken:\n            return \"Claude OAuth access token missing. Run `claude` to authenticate.\"\n        case .notFound:\n            return \"Claude OAuth credentials not found. Run `claude` to authenticate.\"\n        case let .keychainError(status):\n            #if os(macOS)\n            if status == Int(errSecUserCanceled)\n                || status == Int(errSecAuthFailed)\n                || status == Int(errSecInteractionNotAllowed)\n                || status == Int(errSecNoAccessForItem)\n            {\n                return \"Claude Keychain access was denied. CodexBar will back off in the background until you retry \"\n                    + \"via a user action (menu open / manual refresh). \"\n                    + \"Switch Claude Usage source to Web/CLI, or allow access in Keychain Access.\"\n            }\n            #endif\n            return \"Claude OAuth keychain error: \\(status)\"\n        case let .readFailed(message):\n            return \"Claude OAuth credentials read failed: \\(message)\"\n        case let .refreshFailed(message):\n            return \"Claude OAuth token refresh failed: \\(message)\"\n        case .noRefreshToken:\n            return \"Claude OAuth refresh token missing. Run `claude` to authenticate.\"\n        case .refreshDelegatedToClaudeCLI:\n            return \"Claude OAuth refresh is delegated to Claude CLI.\"\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthCredentials+Hashing.swift",
    "content": "import Foundation\n\n#if canImport(CryptoKit)\nimport CryptoKit\n#endif\n\nextension ClaudeOAuthCredentialsStore {\n    static func sha256Prefix(_ data: Data) -> String? {\n        #if canImport(CryptoKit)\n        let digest = SHA256.hash(data: data)\n        let hex = digest.compactMap { String(format: \"%02x\", $0) }.joined()\n        return String(hex.prefix(12))\n        #else\n        _ = data\n        return nil\n        #endif\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthCredentials+SecurityCLIReader.swift",
    "content": "import Dispatch\nimport Foundation\n\n#if canImport(Darwin)\nimport Darwin\n#elseif canImport(Glibc)\nimport Glibc\n#endif\n\nextension ClaudeOAuthCredentialsStore {\n    private static let securityBinaryPath = \"/usr/bin/security\"\n    private static let securityCLIReadTimeout: TimeInterval = 1.5\n\n    struct SecurityCLIReadRequest {\n        let account: String?\n    }\n\n    static func shouldPreferSecurityCLIKeychainRead(\n        readStrategy: ClaudeOAuthKeychainReadStrategy = ClaudeOAuthKeychainReadStrategyPreference.current())\n        -> Bool\n    {\n        readStrategy == .securityCLIExperimental\n    }\n\n    #if os(macOS)\n    private enum SecurityCLIReadError: Error {\n        case binaryUnavailable\n        case launchFailed\n        case timedOut\n        case nonZeroExit(status: Int32, stderrLength: Int)\n    }\n\n    private struct SecurityCLIReadCommandResult {\n        let status: Int32\n        let stdout: Data\n        let stderrLength: Int\n        let durationMs: Double\n    }\n\n    /// Attempts a Claude keychain read via `/usr/bin/security` when the experimental reader is enabled.\n    /// - Important: `interaction` is diagnostics context only and does not gate CLI execution.\n    static func loadFromClaudeKeychainViaSecurityCLIIfEnabled(\n        interaction: ProviderInteraction,\n        readStrategy: ClaudeOAuthKeychainReadStrategy = ClaudeOAuthKeychainReadStrategyPreference.current())\n        -> Data?\n    {\n        guard self.shouldPreferSecurityCLIKeychainRead(readStrategy: readStrategy) else { return nil }\n        let interactionMetadata = interaction == .userInitiated ? \"user\" : \"background\"\n\n        do {\n            let preferredAccount = self.preferredClaudeKeychainAccountForSecurityCLIRead(\n                interaction: interaction)\n            let output: Data\n            let status: Int32\n            let stderrLength: Int\n            let durationMs: Double\n            #if DEBUG\n            if let override = self.taskSecurityCLIReadOverride ?? self.securityCLIReadOverride {\n                switch override {\n                case let .data(data):\n                    output = data ?? Data()\n                    status = 0\n                    stderrLength = 0\n                    durationMs = 0\n                case .timedOut:\n                    throw SecurityCLIReadError.timedOut\n                case .nonZeroExit:\n                    throw SecurityCLIReadError.nonZeroExit(status: 1, stderrLength: 0)\n                case let .dynamic(read):\n                    output = read(SecurityCLIReadRequest(account: preferredAccount)) ?? Data()\n                    status = 0\n                    stderrLength = 0\n                    durationMs = 0\n                }\n            } else {\n                let result = try self.runClaudeSecurityCLIRead(\n                    timeout: self.securityCLIReadTimeout,\n                    account: preferredAccount)\n                output = result.stdout\n                status = result.status\n                stderrLength = result.stderrLength\n                durationMs = result.durationMs\n            }\n            #else\n            let result = try self.runClaudeSecurityCLIRead(\n                timeout: self.securityCLIReadTimeout,\n                account: preferredAccount)\n            output = result.stdout\n            status = result.status\n            stderrLength = result.stderrLength\n            durationMs = result.durationMs\n            #endif\n\n            let sanitized = self.sanitizeSecurityCLIOutput(output)\n            guard !sanitized.isEmpty else { return nil }\n            let parsedCredentials: ClaudeOAuthCredentials\n            do {\n                parsedCredentials = try ClaudeOAuthCredentials.parse(data: sanitized)\n            } catch {\n                self.log.warning(\n                    \"Claude keychain security CLI output invalid; falling back\",\n                    metadata: [\n                        \"reader\": \"securityCLI\",\n                        \"callerInteraction\": interactionMetadata,\n                        \"status\": \"\\(status)\",\n                        \"duration_ms\": String(format: \"%.2f\", durationMs),\n                        \"stderr_length\": \"\\(stderrLength)\",\n                        \"payload_bytes\": \"\\(sanitized.count)\",\n                        \"parse_error_type\": String(describing: type(of: error)),\n                    ])\n                return nil\n            }\n\n            var metadata: [String: String] = [\n                \"reader\": \"securityCLI\",\n                \"callerInteraction\": interactionMetadata,\n                \"status\": \"\\(status)\",\n                \"duration_ms\": String(format: \"%.2f\", durationMs),\n                \"stderr_length\": \"\\(stderrLength)\",\n                \"payload_bytes\": \"\\(sanitized.count)\",\n                \"accountPinned\": preferredAccount == nil ? \"0\" : \"1\",\n            ]\n            for (key, value) in parsedCredentials.diagnosticsMetadata(now: Date()) {\n                metadata[key] = value\n            }\n            self.log.debug(\n                \"Claude keychain security CLI read succeeded\",\n                metadata: metadata)\n            return sanitized\n        } catch let error as SecurityCLIReadError {\n            var metadata: [String: String] = [\n                \"reader\": \"securityCLI\",\n                \"callerInteraction\": interactionMetadata,\n                \"error_type\": String(describing: type(of: error)),\n            ]\n            switch error {\n            case .binaryUnavailable:\n                metadata[\"reason\"] = \"binaryUnavailable\"\n            case .launchFailed:\n                metadata[\"reason\"] = \"launchFailed\"\n            case .timedOut:\n                metadata[\"reason\"] = \"timedOut\"\n            case let .nonZeroExit(status, stderrLength):\n                metadata[\"reason\"] = \"nonZeroExit\"\n                metadata[\"status\"] = \"\\(status)\"\n                metadata[\"stderr_length\"] = \"\\(stderrLength)\"\n            }\n            self.log.warning(\"Claude keychain security CLI read failed; falling back\", metadata: metadata)\n            return nil\n        } catch {\n            self.log.warning(\n                \"Claude keychain security CLI read failed; falling back\",\n                metadata: [\n                    \"reader\": \"securityCLI\",\n                    \"callerInteraction\": interactionMetadata,\n                    \"error_type\": String(describing: type(of: error)),\n                ])\n            return nil\n        }\n    }\n\n    private static func sanitizeSecurityCLIOutput(_ data: Data) -> Data {\n        var sanitized = data\n        while let last = sanitized.last, last == 0x0A || last == 0x0D {\n            sanitized.removeLast()\n        }\n        return sanitized\n    }\n\n    private static func runClaudeSecurityCLIRead(\n        timeout: TimeInterval,\n        account: String?) throws -> SecurityCLIReadCommandResult\n    {\n        guard FileManager.default.isExecutableFile(atPath: self.securityBinaryPath) else {\n            throw SecurityCLIReadError.binaryUnavailable\n        }\n\n        var arguments = [\n            \"find-generic-password\",\n            \"-s\",\n            self.claudeKeychainService,\n        ]\n        if let account, !account.isEmpty {\n            arguments.append(contentsOf: [\"-a\", account])\n        }\n        arguments.append(\"-w\")\n\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: self.securityBinaryPath)\n        process.arguments = arguments\n\n        let stdoutPipe = Pipe()\n        let stderrPipe = Pipe()\n        process.standardOutput = stdoutPipe\n        process.standardError = stderrPipe\n        process.standardInput = nil\n\n        let startedAt = DispatchTime.now().uptimeNanoseconds\n        do {\n            try process.run()\n        } catch {\n            throw SecurityCLIReadError.launchFailed\n        }\n\n        var processGroup: pid_t?\n        let pid = process.processIdentifier\n        if setpgid(pid, pid) == 0 {\n            processGroup = pid\n        }\n\n        let deadline = Date().addingTimeInterval(timeout)\n        while process.isRunning, Date() < deadline {\n            Thread.sleep(forTimeInterval: 0.02)\n        }\n\n        if process.isRunning {\n            self.terminate(process: process, processGroup: processGroup)\n            throw SecurityCLIReadError.timedOut\n        }\n\n        let stdout = stdoutPipe.fileHandleForReading.readDataToEndOfFile()\n        let stderr = stderrPipe.fileHandleForReading.readDataToEndOfFile()\n        let status = process.terminationStatus\n        let durationMs = Double(DispatchTime.now().uptimeNanoseconds - startedAt) / 1_000_000.0\n        guard status == 0 else {\n            throw SecurityCLIReadError.nonZeroExit(status: status, stderrLength: stderr.count)\n        }\n\n        return SecurityCLIReadCommandResult(\n            status: status,\n            stdout: stdout,\n            stderrLength: stderr.count,\n            durationMs: durationMs)\n    }\n\n    private static func terminate(process: Process, processGroup: pid_t?) {\n        guard process.isRunning else { return }\n        process.terminate()\n        if let processGroup {\n            kill(-processGroup, SIGTERM)\n        }\n        let deadline = Date().addingTimeInterval(0.4)\n        while process.isRunning, Date() < deadline {\n            usleep(50000)\n        }\n        if process.isRunning {\n            if let processGroup {\n                kill(-processGroup, SIGKILL)\n            }\n            kill(process.processIdentifier, SIGKILL)\n        }\n    }\n    #else\n    static func loadFromClaudeKeychainViaSecurityCLIIfEnabled(\n        interaction _: ProviderInteraction,\n        readStrategy _: ClaudeOAuthKeychainReadStrategy = ClaudeOAuthKeychainReadStrategyPreference.current())\n        -> Data?\n    {\n        nil\n    }\n    #endif\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthCredentials+TestingOverrides.swift",
    "content": "import Foundation\n\n#if DEBUG\nextension ClaudeOAuthCredentialsStore {\n    nonisolated(unsafe) static var claudeKeychainDataOverride: Data?\n    nonisolated(unsafe) static var claudeKeychainFingerprintOverride: ClaudeKeychainFingerprint?\n    @TaskLocal static var taskClaudeKeychainDataOverride: Data?\n    @TaskLocal static var taskClaudeKeychainFingerprintOverride: ClaudeKeychainFingerprint?\n    @TaskLocal static var taskMemoryCacheStoreOverride: MemoryCacheStore?\n    @TaskLocal static var taskClaudeKeychainFingerprintStoreOverride: ClaudeKeychainFingerprintStore?\n\n    final class ClaudeKeychainFingerprintStore: @unchecked Sendable {\n        var fingerprint: ClaudeKeychainFingerprint?\n\n        init(fingerprint: ClaudeKeychainFingerprint? = nil) {\n            self.fingerprint = fingerprint\n        }\n    }\n\n    final class MemoryCacheStore: @unchecked Sendable {\n        var record: ClaudeOAuthCredentialRecord?\n        var timestamp: Date?\n    }\n\n    static func setClaudeKeychainDataOverrideForTesting(_ data: Data?) {\n        self.claudeKeychainDataOverride = data\n    }\n\n    static func setClaudeKeychainFingerprintOverrideForTesting(_ fingerprint: ClaudeKeychainFingerprint?) {\n        self.claudeKeychainFingerprintOverride = fingerprint\n    }\n\n    static func withClaudeKeychainOverridesForTesting<T>(\n        data: Data?,\n        fingerprint: ClaudeKeychainFingerprint?,\n        operation: () throws -> T) rethrows -> T\n    {\n        try self.$taskClaudeKeychainDataOverride.withValue(data) {\n            try self.$taskClaudeKeychainFingerprintOverride.withValue(fingerprint) {\n                try operation()\n            }\n        }\n    }\n\n    static func withClaudeKeychainOverridesForTesting<T>(\n        data: Data?,\n        fingerprint: ClaudeKeychainFingerprint?,\n        operation: () async throws -> T) async rethrows -> T\n    {\n        try await self.$taskClaudeKeychainDataOverride.withValue(data) {\n            try await self.$taskClaudeKeychainFingerprintOverride.withValue(fingerprint) {\n                try await operation()\n            }\n        }\n    }\n\n    static func withClaudeKeychainFingerprintStoreOverrideForTesting<T>(\n        _ store: ClaudeKeychainFingerprintStore?,\n        operation: () throws -> T) rethrows -> T\n    {\n        try self.$taskClaudeKeychainFingerprintStoreOverride.withValue(store) {\n            try operation()\n        }\n    }\n\n    static func withClaudeKeychainFingerprintStoreOverrideForTesting<T>(\n        _ store: ClaudeKeychainFingerprintStore?,\n        operation: () async throws -> T) async rethrows -> T\n    {\n        try await self.$taskClaudeKeychainFingerprintStoreOverride.withValue(store) {\n            try await operation()\n        }\n    }\n\n    static func withIsolatedMemoryCacheForTesting<T>(operation: () throws -> T) rethrows -> T {\n        let store = MemoryCacheStore()\n        return try self.$taskMemoryCacheStoreOverride.withValue(store) {\n            try operation()\n        }\n    }\n\n    static func withIsolatedMemoryCacheForTesting<T>(operation: () async throws -> T) async rethrows -> T {\n        let store = MemoryCacheStore()\n        return try await self.$taskMemoryCacheStoreOverride.withValue(store) {\n            try await operation()\n        }\n    }\n\n    final class CredentialsFileFingerprintStore: @unchecked Sendable {\n        var fingerprint: CredentialsFileFingerprint?\n\n        init(fingerprint: CredentialsFileFingerprint? = nil) {\n            self.fingerprint = fingerprint\n        }\n\n        func load() -> CredentialsFileFingerprint? {\n            self.fingerprint\n        }\n\n        func save(_ fingerprint: CredentialsFileFingerprint?) {\n            self.fingerprint = fingerprint\n        }\n    }\n\n    enum SecurityCLIReadOverride {\n        case data(Data?)\n        case timedOut\n        case nonZeroExit\n        case dynamic(@Sendable (SecurityCLIReadRequest) -> Data?)\n    }\n\n    @TaskLocal static var taskKeychainAccessOverride: Bool?\n    @TaskLocal static var taskCredentialsFileFingerprintStoreOverride: CredentialsFileFingerprintStore?\n    @TaskLocal static var taskSecurityCLIReadOverride: SecurityCLIReadOverride?\n    @TaskLocal static var taskSecurityCLIReadAccountOverride: String?\n    nonisolated(unsafe) static var securityCLIReadOverride: SecurityCLIReadOverride?\n\n    public struct TestingOverridesSnapshot: Sendable {\n        let keychainOverrideStore: ClaudeKeychainOverrideStore?\n        let keychainData: Data?\n        let keychainFingerprint: ClaudeKeychainFingerprint?\n        let memoryCacheStore: MemoryCacheStore?\n        let fingerprintStore: ClaudeKeychainFingerprintStore?\n        let keychainAccessOverride: Bool?\n        let credentialsFileFingerprintStore: CredentialsFileFingerprintStore?\n        let securityCLIReadOverride: SecurityCLIReadOverride?\n        let securityCLIReadAccountOverride: String?\n\n        init(\n            keychainOverrideStore: ClaudeKeychainOverrideStore?,\n            keychainData: Data?,\n            keychainFingerprint: ClaudeKeychainFingerprint?,\n            memoryCacheStore: MemoryCacheStore?,\n            fingerprintStore: ClaudeKeychainFingerprintStore?,\n            keychainAccessOverride: Bool?,\n            credentialsFileFingerprintStore: CredentialsFileFingerprintStore?,\n            securityCLIReadOverride: SecurityCLIReadOverride?,\n            securityCLIReadAccountOverride: String?)\n        {\n            self.keychainOverrideStore = keychainOverrideStore\n            self.keychainData = keychainData\n            self.keychainFingerprint = keychainFingerprint\n            self.memoryCacheStore = memoryCacheStore\n            self.fingerprintStore = fingerprintStore\n            self.keychainAccessOverride = keychainAccessOverride\n            self.credentialsFileFingerprintStore = credentialsFileFingerprintStore\n            self.securityCLIReadOverride = securityCLIReadOverride\n            self.securityCLIReadAccountOverride = securityCLIReadAccountOverride\n        }\n    }\n\n    static func withKeychainAccessOverrideForTesting<T>(\n        _ disabled: Bool?,\n        operation: () throws -> T) rethrows -> T\n    {\n        try self.$taskKeychainAccessOverride.withValue(disabled) {\n            try operation()\n        }\n    }\n\n    static func withKeychainAccessOverrideForTesting<T>(\n        _ disabled: Bool?,\n        operation: () async throws -> T) async rethrows -> T\n    {\n        try await self.$taskKeychainAccessOverride.withValue(disabled) {\n            try await operation()\n        }\n    }\n\n    fileprivate static func withCredentialsFileFingerprintStoreOverrideForTesting<T>(\n        _ store: CredentialsFileFingerprintStore?,\n        operation: () throws -> T) rethrows -> T\n    {\n        try self.$taskCredentialsFileFingerprintStoreOverride.withValue(store) {\n            try operation()\n        }\n    }\n\n    fileprivate static func withCredentialsFileFingerprintStoreOverrideForTesting<T>(\n        _ store: CredentialsFileFingerprintStore?,\n        operation: () async throws -> T) async rethrows -> T\n    {\n        try await self.$taskCredentialsFileFingerprintStoreOverride.withValue(store) {\n            try await operation()\n        }\n    }\n\n    static func withIsolatedCredentialsFileTrackingForTesting<T>(\n        operation: () throws -> T) rethrows -> T\n    {\n        let store = CredentialsFileFingerprintStore()\n        return try self.$taskCredentialsFileFingerprintStoreOverride.withValue(store) {\n            try operation()\n        }\n    }\n\n    static func withIsolatedCredentialsFileTrackingForTesting<T>(\n        operation: () async throws -> T) async rethrows -> T\n    {\n        let store = CredentialsFileFingerprintStore()\n        return try await self.$taskCredentialsFileFingerprintStoreOverride.withValue(store) {\n            try await operation()\n        }\n    }\n\n    static func withSecurityCLIReadOverrideForTesting<T>(\n        _ readOverride: SecurityCLIReadOverride?,\n        operation: () throws -> T) rethrows -> T\n    {\n        try self.$taskSecurityCLIReadOverride.withValue(readOverride) {\n            try operation()\n        }\n    }\n\n    static func withSecurityCLIReadOverrideForTesting<T>(\n        _ readOverride: SecurityCLIReadOverride?,\n        operation: () async throws -> T) async rethrows -> T\n    {\n        try await self.$taskSecurityCLIReadOverride.withValue(readOverride) {\n            try await operation()\n        }\n    }\n\n    static func currentSecurityCLIReadOverrideForTesting() -> SecurityCLIReadOverride? {\n        self.taskSecurityCLIReadOverride ?? self.securityCLIReadOverride\n    }\n\n    static func withSecurityCLIReadAccountOverrideForTesting<T>(\n        _ account: String?,\n        operation: () throws -> T) rethrows -> T\n    {\n        try self.$taskSecurityCLIReadAccountOverride.withValue(account) {\n            try operation()\n        }\n    }\n\n    static func withSecurityCLIReadAccountOverrideForTesting<T>(\n        _ account: String?,\n        operation: () async throws -> T) async rethrows -> T\n    {\n        try await self.$taskSecurityCLIReadAccountOverride.withValue(account) {\n            try await operation()\n        }\n    }\n\n    public static func withCurrentTestingOverridesForTask<T>(\n        operation: () async throws -> T) async rethrows -> T\n    {\n        try await self.withTestingOverridesSnapshotForTask(\n            self.currentTestingOverridesSnapshotForTask,\n            operation: operation)\n    }\n\n    public static func withCurrentTestingOverridesForTask<T>(\n        operation: () throws -> T) rethrows -> T\n    {\n        try self.withTestingOverridesSnapshotForTask(\n            self.currentTestingOverridesSnapshotForTask,\n            operation: operation)\n    }\n\n    public static var currentTestingOverridesSnapshotForTask: TestingOverridesSnapshot {\n        TestingOverridesSnapshot(\n            keychainOverrideStore: self.taskClaudeKeychainOverrideStore,\n            keychainData: self.taskClaudeKeychainDataOverride,\n            keychainFingerprint: self.taskClaudeKeychainFingerprintOverride,\n            memoryCacheStore: self.taskMemoryCacheStoreOverride,\n            fingerprintStore: self.taskClaudeKeychainFingerprintStoreOverride,\n            keychainAccessOverride: self.taskKeychainAccessOverride,\n            credentialsFileFingerprintStore: self.taskCredentialsFileFingerprintStoreOverride,\n            securityCLIReadOverride: self.taskSecurityCLIReadOverride,\n            securityCLIReadAccountOverride: self.taskSecurityCLIReadAccountOverride)\n    }\n\n    public static func withTestingOverridesSnapshotForTask<T>(\n        _ snapshot: TestingOverridesSnapshot,\n        operation: () async throws -> T) async rethrows -> T\n    {\n        try await self.$taskClaudeKeychainOverrideStore.withValue(snapshot.keychainOverrideStore) {\n            try await self.$taskClaudeKeychainDataOverride.withValue(snapshot.keychainData) {\n                try await self.$taskClaudeKeychainFingerprintOverride.withValue(snapshot.keychainFingerprint) {\n                    try await self.$taskMemoryCacheStoreOverride.withValue(snapshot.memoryCacheStore) {\n                        try await self.$taskClaudeKeychainFingerprintStoreOverride\n                            .withValue(snapshot.fingerprintStore) {\n                                try await self.$taskKeychainAccessOverride.withValue(snapshot.keychainAccessOverride) {\n                                    try await self.$taskCredentialsFileFingerprintStoreOverride.withValue(\n                                        snapshot.credentialsFileFingerprintStore)\n                                    {\n                                        try await self.$taskSecurityCLIReadOverride.withValue(\n                                            snapshot.securityCLIReadOverride)\n                                        {\n                                            try await self.$taskSecurityCLIReadAccountOverride.withValue(\n                                                snapshot.securityCLIReadAccountOverride)\n                                            {\n                                                try await operation()\n                                            }\n                                        }\n                                    }\n                                }\n                            }\n                    }\n                }\n            }\n        }\n    }\n\n    public static func withTestingOverridesSnapshotForTask<T>(\n        _ snapshot: TestingOverridesSnapshot,\n        operation: () throws -> T) rethrows -> T\n    {\n        try self.$taskClaudeKeychainOverrideStore.withValue(snapshot.keychainOverrideStore) {\n            try self.$taskClaudeKeychainDataOverride.withValue(snapshot.keychainData) {\n                try self.$taskClaudeKeychainFingerprintOverride.withValue(snapshot.keychainFingerprint) {\n                    try self.$taskMemoryCacheStoreOverride.withValue(snapshot.memoryCacheStore) {\n                        try self.$taskClaudeKeychainFingerprintStoreOverride.withValue(snapshot.fingerprintStore) {\n                            try self.$taskKeychainAccessOverride.withValue(snapshot.keychainAccessOverride) {\n                                try self.$taskCredentialsFileFingerprintStoreOverride.withValue(\n                                    snapshot.credentialsFileFingerprintStore)\n                                {\n                                    try self.$taskSecurityCLIReadOverride.withValue(\n                                        snapshot.securityCLIReadOverride)\n                                    {\n                                        try self.$taskSecurityCLIReadAccountOverride.withValue(\n                                            snapshot.securityCLIReadAccountOverride)\n                                        {\n                                            try operation()\n                                        }\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    static func setSecurityCLIReadOverrideForTesting(_ readOverride: SecurityCLIReadOverride?) {\n        self.securityCLIReadOverride = readOverride\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthCredentials.swift",
    "content": "import Dispatch\nimport Foundation\n\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n\n#if os(macOS)\nimport LocalAuthentication\nimport Security\n#endif\n\n// swiftlint:disable type_body_length file_length\npublic enum ClaudeOAuthCredentialsStore {\n    private static let credentialsPath = \".claude/.credentials.json\"\n    static let claudeKeychainService = \"Claude Code-credentials\"\n    private static let cacheKey = KeychainCacheStore.Key.oauth(provider: .claude)\n    public static let environmentTokenKey = \"CODEXBAR_CLAUDE_OAUTH_TOKEN\"\n    public static let environmentScopesKey = \"CODEXBAR_CLAUDE_OAUTH_SCOPES\"\n\n    // Claude CLI's OAuth client ID - this is a public identifier (not a secret).\n    // It's the same client ID used by Claude Code CLI for OAuth PKCE flow.\n    // Can be overridden via environment variable if Anthropic ever changes it.\n    public static let defaultOAuthClientID = \"9d1c250a-e61b-44d9-88ed-5944d1962f5e\"\n    public static let environmentClientIDKey = \"CODEXBAR_CLAUDE_OAUTH_CLIENT_ID\"\n    private static let tokenRefreshEndpoint = \"https://platform.claude.com/v1/oauth/token\"\n\n    private static var oauthClientID: String {\n        ProcessInfo.processInfo.environment[self.environmentClientIDKey]?\n            .trimmingCharacters(in: .whitespacesAndNewlines)\n            ?? self.defaultOAuthClientID\n    }\n\n    static let log = CodexBarLog.logger(LogCategories.claudeUsage)\n    private static let fileFingerprintKey = \"ClaudeOAuthCredentialsFileFingerprintV2\"\n    private static let claudeKeychainPromptLock = NSLock()\n    private static let claudeKeychainFingerprintKey = \"ClaudeOAuthClaudeKeychainFingerprintV2\"\n    private static let claudeKeychainFingerprintLegacyKey = \"ClaudeOAuthClaudeKeychainFingerprintV1\"\n    private static let claudeKeychainChangeCheckLock = NSLock()\n    private nonisolated(unsafe) static var lastClaudeKeychainChangeCheckAt: Date?\n    private static let claudeKeychainChangeCheckMinimumInterval: TimeInterval = 60\n    private static let reauthenticateHint = \"Run `claude` to re-authenticate.\"\n\n    struct ClaudeKeychainFingerprint: Codable, Equatable {\n        let modifiedAt: Int?\n        let createdAt: Int?\n        let persistentRefHash: String?\n    }\n\n    struct CredentialsFileFingerprint: Codable, Equatable {\n        let modifiedAtMs: Int?\n        let size: Int\n    }\n\n    struct CacheEntry: Codable {\n        let data: Data\n        let storedAt: Date\n        let owner: ClaudeOAuthCredentialOwner?\n\n        init(data: Data, storedAt: Date, owner: ClaudeOAuthCredentialOwner? = nil) {\n            self.data = data\n            self.storedAt = storedAt\n            self.owner = owner\n        }\n    }\n\n    private nonisolated(unsafe) static var credentialsURLOverride: URL?\n    #if DEBUG\n    @TaskLocal private static var taskCredentialsURLOverride: URL?\n    #endif\n    @TaskLocal static var allowBackgroundPromptBootstrap: Bool = false\n    // In-memory cache (nonisolated for synchronous access)\n    private static let memoryCacheLock = NSLock()\n    private nonisolated(unsafe) static var cachedCredentialRecord: ClaudeOAuthCredentialRecord?\n    private nonisolated(unsafe) static var cacheTimestamp: Date?\n    private static let memoryCacheValidityDuration: TimeInterval = 1800\n\n    private static func readMemoryCache() -> (record: ClaudeOAuthCredentialRecord?, timestamp: Date?) {\n        #if DEBUG\n        if let store = self.taskMemoryCacheStoreOverride {\n            return (store.record, store.timestamp)\n        }\n        #endif\n        self.memoryCacheLock.lock()\n        defer { self.memoryCacheLock.unlock() }\n        return (self.cachedCredentialRecord, self.cacheTimestamp)\n    }\n\n    private static func writeMemoryCache(record: ClaudeOAuthCredentialRecord?, timestamp: Date?) {\n        #if DEBUG\n        if let store = self.taskMemoryCacheStoreOverride {\n            store.record = record\n            store.timestamp = timestamp\n            return\n        }\n        #endif\n        self.memoryCacheLock.lock()\n        self.cachedCredentialRecord = record\n        self.cacheTimestamp = timestamp\n        self.memoryCacheLock.unlock()\n    }\n\n    private struct CollaboratorContext: Sendable {\n        let allowBackgroundPromptBootstrap: Bool\n        #if DEBUG\n        let credentialsURLOverride: URL?\n        let testingOverrides: TestingOverridesSnapshot\n        #endif\n\n        func run<T>(_ operation: () throws -> T) rethrows -> T {\n            try ClaudeOAuthCredentialsStore.$allowBackgroundPromptBootstrap\n                .withValue(self.allowBackgroundPromptBootstrap) {\n                    #if DEBUG\n                    try ClaudeOAuthCredentialsStore.withTestingOverridesSnapshotForTask(self.testingOverrides) {\n                        try ClaudeOAuthCredentialsStore\n                            .withCredentialsURLOverrideForTesting(self.credentialsURLOverride) {\n                                try operation()\n                            }\n                    }\n                    #else\n                    try operation()\n                    #endif\n                }\n        }\n\n        func run<T>(_ operation: () async throws -> T) async rethrows -> T {\n            try await ClaudeOAuthCredentialsStore.$allowBackgroundPromptBootstrap\n                .withValue(self.allowBackgroundPromptBootstrap) {\n                    #if DEBUG\n                    try await ClaudeOAuthCredentialsStore.withTestingOverridesSnapshotForTask(self.testingOverrides) {\n                        try await ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(\n                            self.credentialsURLOverride)\n                        {\n                            try await operation()\n                        }\n                    }\n                    #else\n                    try await operation()\n                    #endif\n                }\n        }\n    }\n\n    private static func currentCollaboratorContext() -> CollaboratorContext {\n        #if DEBUG\n        CollaboratorContext(\n            allowBackgroundPromptBootstrap: self.allowBackgroundPromptBootstrap,\n            credentialsURLOverride: self.taskCredentialsURLOverride,\n            testingOverrides: self.currentTestingOverridesSnapshotForTask)\n        #else\n        CollaboratorContext(\n            allowBackgroundPromptBootstrap: self.allowBackgroundPromptBootstrap)\n        #endif\n    }\n\n    private struct Repository: Sendable {\n        let context: CollaboratorContext\n\n        func load(environment: [String: String], allowKeychainPrompt: Bool, respectKeychainPromptCooldown: Bool) throws\n            -> ClaudeOAuthCredentials\n        {\n            try self.loadRecord(\n                environment: environment,\n                allowKeychainPrompt: allowKeychainPrompt,\n                respectKeychainPromptCooldown: respectKeychainPromptCooldown,\n                allowClaudeKeychainRepairWithoutPrompt: true).credentials\n        }\n\n        func loadRecord(\n            environment: [String: String],\n            allowKeychainPrompt: Bool,\n            respectKeychainPromptCooldown: Bool,\n            allowClaudeKeychainRepairWithoutPrompt: Bool) throws -> ClaudeOAuthCredentialRecord\n        {\n            try self.context.run {\n                let shouldRespectKeychainPromptCooldownForSilentProbes =\n                    respectKeychainPromptCooldown || !allowKeychainPrompt\n\n                if let credentials = ClaudeOAuthCredentialsStore.loadFromEnvironment(environment) {\n                    return ClaudeOAuthCredentialRecord(\n                        credentials: credentials,\n                        owner: .environment,\n                        source: .environment)\n                }\n\n                _ = self.invalidateCacheIfCredentialsFileChanged()\n\n                let recovery = Recovery(context: self.context)\n                let memory = ClaudeOAuthCredentialsStore.readMemoryCache()\n                if let cachedRecord = memory.record,\n                   let timestamp = memory.timestamp,\n                   Date().timeIntervalSince(timestamp) < ClaudeOAuthCredentialsStore.memoryCacheValidityDuration,\n                   !cachedRecord.credentials.isExpired\n                {\n                    if recovery.shouldAttemptFreshnessSyncFromClaudeKeychain(cached: cachedRecord),\n                       let synced = recovery.syncWithClaudeKeychainIfChanged(\n                           cached: cachedRecord,\n                           respectKeychainPromptCooldown: shouldRespectKeychainPromptCooldownForSilentProbes)\n                    {\n                        return synced\n                    }\n                    return ClaudeOAuthCredentialRecord(\n                        credentials: cachedRecord.credentials,\n                        owner: cachedRecord.owner,\n                        source: .memoryCache)\n                }\n\n                var lastError: Error?\n                var expiredRecord: ClaudeOAuthCredentialRecord?\n\n                switch KeychainCacheStore.load(key: ClaudeOAuthCredentialsStore.cacheKey, as: CacheEntry.self) {\n                case let .found(entry):\n                    if let creds = try? ClaudeOAuthCredentials.parse(data: entry.data) {\n                        let owner = entry.owner ?? .claudeCLI\n                        let record = ClaudeOAuthCredentialRecord(\n                            credentials: creds,\n                            owner: owner,\n                            source: .cacheKeychain)\n                        if creds.isExpired {\n                            expiredRecord = record\n                        } else {\n                            if recovery.shouldAttemptFreshnessSyncFromClaudeKeychain(cached: record),\n                               let synced = recovery.syncWithClaudeKeychainIfChanged(\n                                   cached: record,\n                                   respectKeychainPromptCooldown: shouldRespectKeychainPromptCooldownForSilentProbes)\n                            {\n                                return synced\n                            }\n                            ClaudeOAuthCredentialsStore.writeMemoryCache(\n                                record: ClaudeOAuthCredentialRecord(\n                                    credentials: creds,\n                                    owner: owner,\n                                    source: .memoryCache),\n                                timestamp: Date())\n                            return record\n                        }\n                    } else {\n                        KeychainCacheStore.clear(key: ClaudeOAuthCredentialsStore.cacheKey)\n                    }\n                case .invalid:\n                    KeychainCacheStore.clear(key: ClaudeOAuthCredentialsStore.cacheKey)\n                case .missing:\n                    break\n                }\n\n                do {\n                    let fileData = try ClaudeOAuthCredentialsStore.loadFromFile()\n                    let creds = try ClaudeOAuthCredentials.parse(data: fileData)\n                    let record = ClaudeOAuthCredentialRecord(\n                        credentials: creds,\n                        owner: .claudeCLI,\n                        source: .credentialsFile)\n                    if creds.isExpired {\n                        expiredRecord = record\n                    } else {\n                        ClaudeOAuthCredentialsStore.writeMemoryCache(\n                            record: ClaudeOAuthCredentialRecord(\n                                credentials: creds,\n                                owner: .claudeCLI,\n                                source: .memoryCache),\n                            timestamp: Date())\n                        ClaudeOAuthCredentialsStore.saveToCacheKeychain(fileData, owner: .claudeCLI)\n                        return record\n                    }\n                } catch let error as ClaudeOAuthCredentialsError {\n                    if case .notFound = error {\n                    } else {\n                        lastError = error\n                    }\n                } catch {\n                    lastError = error\n                }\n\n                if allowClaudeKeychainRepairWithoutPrompt, !allowKeychainPrompt {\n                    if let repaired = recovery.repairFromClaudeKeychainWithoutPromptIfAllowed(\n                        now: Date(),\n                        respectKeychainPromptCooldown: shouldRespectKeychainPromptCooldownForSilentProbes)\n                    {\n                        return repaired\n                    }\n                }\n\n                if let prompted = self.loadFromClaudeKeychainWithPromptIfAllowed(\n                    allowKeychainPrompt: allowKeychainPrompt,\n                    respectKeychainPromptCooldown: respectKeychainPromptCooldown,\n                    lastError: &lastError)\n                {\n                    return prompted\n                }\n\n                if let expiredRecord {\n                    return expiredRecord\n                }\n                if let lastError { throw lastError }\n                throw ClaudeOAuthCredentialsError.notFound\n            }\n        }\n\n        private func loadFromClaudeKeychainWithPromptIfAllowed(\n            allowKeychainPrompt: Bool,\n            respectKeychainPromptCooldown: Bool,\n            lastError: inout Error?) -> ClaudeOAuthCredentialRecord?\n        {\n            let shouldApplyPromptCooldown =\n                ClaudeOAuthCredentialsStore.isPromptPolicyApplicable && respectKeychainPromptCooldown\n            let promptAllowed =\n                allowKeychainPrompt\n                    && (!shouldApplyPromptCooldown || ClaudeOAuthKeychainAccessGate.shouldAllowPrompt())\n            guard promptAllowed else { return nil }\n\n            do {\n                ClaudeOAuthCredentialsStore.claudeKeychainPromptLock.lock()\n                defer { ClaudeOAuthCredentialsStore.claudeKeychainPromptLock.unlock() }\n\n                let memory = ClaudeOAuthCredentialsStore.readMemoryCache()\n                if let cachedRecord = memory.record,\n                   let timestamp = memory.timestamp,\n                   Date().timeIntervalSince(timestamp) < ClaudeOAuthCredentialsStore.memoryCacheValidityDuration,\n                   !cachedRecord.credentials.isExpired\n                {\n                    return ClaudeOAuthCredentialRecord(\n                        credentials: cachedRecord.credentials,\n                        owner: cachedRecord.owner,\n                        source: .memoryCache)\n                }\n                if case let .found(entry) = KeychainCacheStore.load(\n                    key: ClaudeOAuthCredentialsStore.cacheKey,\n                    as: CacheEntry.self),\n                    let creds = try? ClaudeOAuthCredentials.parse(data: entry.data),\n                    !creds.isExpired\n                {\n                    return ClaudeOAuthCredentialRecord(\n                        credentials: creds,\n                        owner: entry.owner ?? .claudeCLI,\n                        source: .cacheKeychain)\n                }\n\n                let promptMode = ClaudeOAuthKeychainPromptPreference.current()\n                guard ClaudeOAuthCredentialsStore.shouldAllowClaudeCodeKeychainAccess(mode: promptMode) else {\n                    return nil\n                }\n\n                if ClaudeOAuthCredentialsStore.shouldPreferSecurityCLIKeychainRead(),\n                   let keychainData = ClaudeOAuthCredentialsStore.loadFromClaudeKeychainViaSecurityCLIIfEnabled(\n                       interaction: ProviderInteractionContext.current)\n                {\n                    let creds = try ClaudeOAuthCredentials.parse(data: keychainData)\n                    let record = ClaudeOAuthCredentialRecord(\n                        credentials: creds,\n                        owner: .claudeCLI,\n                        source: .claudeKeychain)\n                    ClaudeOAuthCredentialsStore.writeMemoryCache(\n                        record: ClaudeOAuthCredentialRecord(\n                            credentials: creds,\n                            owner: .claudeCLI,\n                            source: .memoryCache),\n                        timestamp: Date())\n                    ClaudeOAuthCredentialsStore.saveToCacheKeychain(keychainData, owner: .claudeCLI)\n                    return record\n                }\n\n                let shouldPreferSecurityCLIKeychainRead =\n                    ClaudeOAuthCredentialsStore.shouldPreferSecurityCLIKeychainRead()\n                var fallbackPromptMode = promptMode\n                if shouldPreferSecurityCLIKeychainRead {\n                    fallbackPromptMode = ClaudeOAuthKeychainPromptPreference.securityFrameworkFallbackMode()\n                    let fallbackDecision = ClaudeOAuthCredentialsStore.securityFrameworkFallbackPromptDecision(\n                        promptMode: fallbackPromptMode,\n                        allowKeychainPrompt: allowKeychainPrompt,\n                        respectKeychainPromptCooldown: respectKeychainPromptCooldown)\n                    ClaudeOAuthCredentialsStore.log.debug(\n                        \"Claude keychain Security.framework fallback prompt policy evaluated\",\n                        metadata: [\n                            \"reader\": \"securityFrameworkFallback\",\n                            \"fallbackPromptMode\": fallbackPromptMode.rawValue,\n                            \"fallbackPromptAllowed\": \"\\(fallbackDecision.allowed)\",\n                            \"fallbackBlockedReason\": fallbackDecision.blockedReason ?? \"none\",\n                        ])\n                    guard fallbackDecision.allowed else { return nil }\n                }\n\n                if ClaudeOAuthCredentialsStore.shouldShowClaudeKeychainPreAlert() {\n                    KeychainPromptHandler.notify(\n                        KeychainPromptContext(\n                            kind: .claudeOAuth,\n                            service: ClaudeOAuthCredentialsStore.claudeKeychainService,\n                            account: nil))\n                }\n                let keychainData: Data = if shouldPreferSecurityCLIKeychainRead {\n                    try ClaudeOAuthCredentialsStore.loadFromClaudeKeychainUsingSecurityFramework(\n                        promptMode: fallbackPromptMode,\n                        allowKeychainPrompt: true)\n                } else {\n                    try ClaudeOAuthCredentialsStore.loadFromClaudeKeychain()\n                }\n                let creds = try ClaudeOAuthCredentials.parse(data: keychainData)\n                let record = ClaudeOAuthCredentialRecord(\n                    credentials: creds,\n                    owner: .claudeCLI,\n                    source: .claudeKeychain)\n                ClaudeOAuthCredentialsStore.writeMemoryCache(\n                    record: ClaudeOAuthCredentialRecord(\n                        credentials: creds,\n                        owner: .claudeCLI,\n                        source: .memoryCache),\n                    timestamp: Date())\n                ClaudeOAuthCredentialsStore.saveToCacheKeychain(keychainData, owner: .claudeCLI)\n                return record\n            } catch let error as ClaudeOAuthCredentialsError {\n                if case .notFound = error {\n                } else {\n                    lastError = error\n                }\n            } catch {\n                lastError = error\n            }\n            return nil\n        }\n\n        @discardableResult\n        func invalidateCacheIfCredentialsFileChanged() -> Bool {\n            self.context.run {\n                let current = ClaudeOAuthCredentialsStore.currentFileFingerprint()\n                let stored = ClaudeOAuthCredentialsStore.loadFileFingerprint()\n                guard current != stored else { return false }\n                ClaudeOAuthCredentialsStore.saveFileFingerprint(current)\n                ClaudeOAuthCredentialsStore.log.info(\"Claude OAuth credentials file changed; invalidating cache\")\n\n                ClaudeOAuthCredentialsStore.writeMemoryCache(record: nil, timestamp: nil)\n\n                var shouldClearKeychainCache = false\n                if let current {\n                    if let modifiedAtMs = current.modifiedAtMs {\n                        let modifiedAt = Date(timeIntervalSince1970: TimeInterval(Double(modifiedAtMs) / 1000.0))\n                        if case let .found(entry) = KeychainCacheStore.load(\n                            key: ClaudeOAuthCredentialsStore.cacheKey,\n                            as: CacheEntry.self)\n                        {\n                            if entry.storedAt < modifiedAt {\n                                shouldClearKeychainCache = true\n                            }\n                        } else {\n                            shouldClearKeychainCache = true\n                        }\n                    } else {\n                        shouldClearKeychainCache = true\n                    }\n                } else {\n                    shouldClearKeychainCache = true\n                }\n\n                if shouldClearKeychainCache {\n                    ClaudeOAuthCredentialsStore.clearCacheKeychain()\n                }\n                return true\n            }\n        }\n\n        func invalidateCache() {\n            self.context.run {\n                ClaudeOAuthCredentialsStore.writeMemoryCache(record: nil, timestamp: nil)\n                ClaudeOAuthCredentialsStore.clearCacheKeychain()\n            }\n        }\n\n        func hasCachedCredentials(environment: [String: String]) -> Bool {\n            self.context.run {\n                func isRefreshableOrValid(_ record: ClaudeOAuthCredentialRecord) -> Bool {\n                    let creds = record.credentials\n                    if !creds.isExpired { return true }\n                    switch record.owner {\n                    case .claudeCLI:\n                        return true\n                    case .codexbar:\n                        let refreshToken = creds.refreshToken?.trimmingCharacters(\n                            in: .whitespacesAndNewlines) ?? \"\"\n                        return !refreshToken.isEmpty\n                    case .environment:\n                        return false\n                    }\n                }\n\n                if let creds = ClaudeOAuthCredentialsStore.loadFromEnvironment(environment),\n                   isRefreshableOrValid(\n                       ClaudeOAuthCredentialRecord(\n                           credentials: creds,\n                           owner: .environment,\n                           source: .environment))\n                {\n                    return true\n                }\n\n                let memory = ClaudeOAuthCredentialsStore.readMemoryCache()\n                if let timestamp = memory.timestamp,\n                   let cached = memory.record,\n                   Date().timeIntervalSince(timestamp) < ClaudeOAuthCredentialsStore.memoryCacheValidityDuration,\n                   isRefreshableOrValid(cached)\n                {\n                    return true\n                }\n\n                switch KeychainCacheStore.load(key: ClaudeOAuthCredentialsStore.cacheKey, as: CacheEntry.self) {\n                case let .found(entry):\n                    guard let creds = try? ClaudeOAuthCredentials.parse(data: entry.data) else { return false }\n                    let record = ClaudeOAuthCredentialRecord(\n                        credentials: creds,\n                        owner: entry.owner ?? .claudeCLI,\n                        source: .cacheKeychain)\n                    return isRefreshableOrValid(record)\n                default:\n                    break\n                }\n\n                if let fileData = try? ClaudeOAuthCredentialsStore.loadFromFile(),\n                   let creds = try? ClaudeOAuthCredentials.parse(data: fileData),\n                   isRefreshableOrValid(\n                       ClaudeOAuthCredentialRecord(\n                           credentials: creds,\n                           owner: .claudeCLI,\n                           source: .credentialsFile))\n                {\n                    return true\n                }\n                return false\n            }\n        }\n\n        func hasClaudeKeychainCredentialsWithoutPrompt() -> Bool {\n            self.context.run {\n                #if os(macOS)\n                let mode = ClaudeOAuthKeychainPromptPreference.current()\n                guard ClaudeOAuthCredentialsStore.shouldAllowClaudeCodeKeychainAccess(mode: mode) else { return false }\n                if ClaudeOAuthCredentialsStore.loadFromClaudeKeychainViaSecurityCLIIfEnabled(\n                    interaction: ProviderInteractionContext.current) != nil\n                {\n                    return true\n                }\n\n                let fallbackPromptMode = ClaudeOAuthKeychainPromptPreference.securityFrameworkFallbackMode()\n                guard ClaudeOAuthCredentialsStore.shouldAllowClaudeCodeKeychainAccess(mode: fallbackPromptMode) else {\n                    return false\n                }\n                if ProviderInteractionContext.current == .background,\n                   !ClaudeOAuthKeychainAccessGate.shouldAllowPrompt()\n                {\n                    return false\n                }\n                #if DEBUG\n                if let store = ClaudeOAuthCredentialsStore.taskClaudeKeychainOverrideStore,\n                   let data = store.data\n                {\n                    return (try? ClaudeOAuthCredentials.parse(data: data)) != nil\n                }\n                if let data = ClaudeOAuthCredentialsStore.taskClaudeKeychainDataOverride\n                    ?? ClaudeOAuthCredentialsStore.claudeKeychainDataOverride\n                {\n                    return (try? ClaudeOAuthCredentials.parse(data: data)) != nil\n                }\n                #endif\n\n                var query: [String: Any] = [\n                    kSecClass as String: kSecClassGenericPassword,\n                    kSecAttrService as String: ClaudeOAuthCredentialsStore.claudeKeychainService,\n                    kSecMatchLimit as String: kSecMatchLimitOne,\n                    kSecReturnAttributes as String: true,\n                ]\n                KeychainNoUIQuery.apply(to: &query)\n\n                let (status, _, durationMs) = ClaudeOAuthKeychainQueryTiming.copyMatching(query)\n                if ClaudeOAuthKeychainQueryTiming\n                    .backoffIfSlowNoUIQuery(\n                        durationMs,\n                        ClaudeOAuthCredentialsStore.claudeKeychainService,\n                        ClaudeOAuthCredentialsStore.log)\n                {\n                    return false\n                }\n                switch status {\n                case errSecSuccess, errSecInteractionNotAllowed:\n                    return true\n                case errSecUserCanceled, errSecAuthFailed, errSecNoAccessForItem:\n                    ClaudeOAuthKeychainAccessGate.recordDenied()\n                    return false\n                default:\n                    return false\n                }\n                #else\n                return false\n                #endif\n            }\n        }\n    }\n\n    private struct Recovery: Sendable {\n        let context: CollaboratorContext\n\n        func shouldAttemptFreshnessSyncFromClaudeKeychain(cached: ClaudeOAuthCredentialRecord) -> Bool {\n            guard !cached.credentials.isExpired else { return false }\n            guard cached.owner == .claudeCLI else { return false }\n            guard ClaudeOAuthCredentialsStore.keychainAccessAllowed else { return false }\n\n            let mode = ClaudeOAuthKeychainPromptPreference.storedMode()\n            switch mode {\n            case .never:\n                return false\n            case .onlyOnUserAction:\n                if ProviderInteractionContext.current != .userInitiated {\n                    if ProcessInfo.processInfo.environment[\"CODEXBAR_DEBUG_CLAUDE_OAUTH_FLOW\"] == \"1\" {\n                        ClaudeOAuthCredentialsStore.log.debug(\n                            \"Claude OAuth keychain freshness sync skipped (background)\",\n                            metadata: [\"promptMode\": mode.rawValue, \"owner\": String(describing: cached.owner)])\n                    }\n                    return false\n                }\n                return true\n            case .always:\n                return true\n            }\n        }\n\n        func syncWithClaudeKeychainIfChanged(\n            cached: ClaudeOAuthCredentialRecord,\n            respectKeychainPromptCooldown: Bool,\n            now: Date = Date()) -> ClaudeOAuthCredentialRecord?\n        {\n            #if os(macOS)\n            let mode = ClaudeOAuthKeychainPromptPreference.current()\n            guard ClaudeOAuthCredentialsStore.shouldAllowClaudeCodeKeychainAccess(mode: mode) else { return nil }\n            if ClaudeOAuthCredentialsStore.isPromptPolicyApplicable,\n               respectKeychainPromptCooldown,\n               !ClaudeOAuthKeychainAccessGate.shouldAllowPrompt(now: now)\n            {\n                return nil\n            }\n\n            if ClaudeOAuthCredentialsStore.shouldShowClaudeKeychainPreAlert() {\n                return nil\n            }\n\n            if !ClaudeOAuthCredentialsStore.shouldCheckClaudeKeychainChange(now: now) {\n                return nil\n            }\n\n            guard let currentFingerprint = ClaudeOAuthCredentialsStore.currentClaudeKeychainFingerprintWithoutPrompt()\n            else {\n                return nil\n            }\n            let storedFingerprint = ClaudeOAuthCredentialsStore.loadClaudeKeychainFingerprint()\n            guard currentFingerprint != storedFingerprint else { return nil }\n\n            do {\n                guard let data = try ClaudeOAuthCredentialsStore.loadFromClaudeKeychainNonInteractive() else {\n                    return nil\n                }\n                guard let keychainCreds = try? ClaudeOAuthCredentials.parse(data: data) else {\n                    ClaudeOAuthCredentialsStore.saveClaudeKeychainFingerprint(currentFingerprint)\n                    return nil\n                }\n                ClaudeOAuthCredentialsStore.saveClaudeKeychainFingerprint(currentFingerprint)\n\n                guard keychainCreds.accessToken != cached.credentials.accessToken else { return nil }\n                if keychainCreds.isExpired, !cached.credentials.isExpired { return nil }\n\n                ClaudeOAuthCredentialsStore.log.info(\"Claude keychain credentials changed; syncing OAuth cache\")\n                let synced = ClaudeOAuthCredentialRecord(\n                    credentials: keychainCreds,\n                    owner: .claudeCLI,\n                    source: .claudeKeychain)\n                ClaudeOAuthCredentialsStore.writeMemoryCache(\n                    record: ClaudeOAuthCredentialRecord(\n                        credentials: keychainCreds,\n                        owner: .claudeCLI,\n                        source: .memoryCache),\n                    timestamp: now)\n                ClaudeOAuthCredentialsStore.saveToCacheKeychain(data, owner: .claudeCLI)\n                return synced\n            } catch let error as ClaudeOAuthCredentialsError {\n                if case let .keychainError(status) = error,\n                   status == Int(errSecUserCanceled)\n                   || status == Int(errSecAuthFailed)\n                   || status == Int(errSecInteractionNotAllowed)\n                   || status == Int(errSecNoAccessForItem)\n                {\n                    ClaudeOAuthKeychainAccessGate.recordDenied(now: now)\n                }\n                return nil\n            } catch {\n                return nil\n            }\n            #else\n            _ = cached\n            _ = respectKeychainPromptCooldown\n            _ = now\n            return nil\n            #endif\n        }\n\n        func repairFromClaudeKeychainWithoutPromptIfAllowed(\n            now: Date,\n            respectKeychainPromptCooldown: Bool) -> ClaudeOAuthCredentialRecord?\n        {\n            #if os(macOS)\n            let mode = ClaudeOAuthKeychainPromptPreference.current()\n            guard ClaudeOAuthCredentialsStore.shouldAllowClaudeCodeKeychainAccess(mode: mode) else { return nil }\n\n            if ClaudeOAuthCredentialsStore.shouldShowClaudeKeychainPreAlert() {\n                return nil\n            }\n\n            if ClaudeOAuthCredentialsStore.isPromptPolicyApplicable,\n               respectKeychainPromptCooldown,\n               ProviderInteractionContext.current != .userInitiated,\n               !ClaudeOAuthKeychainAccessGate.shouldAllowPrompt(now: now)\n            {\n                return nil\n            }\n\n            do {\n                if ClaudeOAuthCredentialsStore.shouldPreferSecurityCLIKeychainRead(),\n                   let securityData = ClaudeOAuthCredentialsStore.loadFromClaudeKeychainViaSecurityCLIIfEnabled(\n                       interaction: ProviderInteractionContext.current),\n                   !securityData.isEmpty\n                {\n                    guard let creds = try? ClaudeOAuthCredentials.parse(data: securityData) else { return nil }\n                    if creds.isExpired {\n                        return ClaudeOAuthCredentialRecord(\n                            credentials: creds,\n                            owner: .claudeCLI,\n                            source: .claudeKeychain)\n                    }\n\n                    ClaudeOAuthCredentialsStore.writeMemoryCache(\n                        record: ClaudeOAuthCredentialRecord(\n                            credentials: creds,\n                            owner: .claudeCLI,\n                            source: .memoryCache),\n                        timestamp: now)\n                    ClaudeOAuthCredentialsStore.saveToCacheKeychain(securityData, owner: .claudeCLI)\n\n                    ClaudeOAuthCredentialsStore.log.info(\n                        \"Claude keychain credentials loaded without prompt; syncing OAuth cache\",\n                        metadata: [\"interaction\": ProviderInteractionContext.current == .userInitiated\n                            ? \"user\" : \"background\"])\n                    return ClaudeOAuthCredentialRecord(\n                        credentials: creds,\n                        owner: .claudeCLI,\n                        source: .claudeKeychain)\n                }\n\n                guard let data = try ClaudeOAuthCredentialsStore.loadFromClaudeKeychainNonInteractive(),\n                      !data.isEmpty\n                else {\n                    return nil\n                }\n                let fingerprint = ClaudeOAuthCredentialsStore.currentClaudeKeychainFingerprintWithoutPrompt()\n                guard let creds = try? ClaudeOAuthCredentials.parse(data: data) else {\n                    ClaudeOAuthCredentialsStore.saveClaudeKeychainFingerprint(fingerprint)\n                    return nil\n                }\n\n                if creds.isExpired {\n                    ClaudeOAuthCredentialsStore.saveClaudeKeychainFingerprint(fingerprint)\n                    return ClaudeOAuthCredentialRecord(\n                        credentials: creds,\n                        owner: .claudeCLI,\n                        source: .claudeKeychain)\n                }\n\n                ClaudeOAuthCredentialsStore.saveClaudeKeychainFingerprint(fingerprint)\n                ClaudeOAuthCredentialsStore.writeMemoryCache(\n                    record: ClaudeOAuthCredentialRecord(\n                        credentials: creds,\n                        owner: .claudeCLI,\n                        source: .memoryCache),\n                    timestamp: now)\n                ClaudeOAuthCredentialsStore.saveToCacheKeychain(data, owner: .claudeCLI)\n\n                ClaudeOAuthCredentialsStore.log.info(\n                    \"Claude keychain credentials loaded without prompt; syncing OAuth cache\",\n                    metadata: [\"interaction\": ProviderInteractionContext.current == .userInitiated\n                        ? \"user\" : \"background\"])\n                return ClaudeOAuthCredentialRecord(\n                    credentials: creds,\n                    owner: .claudeCLI,\n                    source: .claudeKeychain)\n            } catch let error as ClaudeOAuthCredentialsError {\n                if case let .keychainError(status) = error,\n                   status == Int(errSecUserCanceled)\n                   || status == Int(errSecAuthFailed)\n                   || status == Int(errSecInteractionNotAllowed)\n                   || status == Int(errSecNoAccessForItem)\n                {\n                    ClaudeOAuthKeychainAccessGate.recordDenied(now: now)\n                }\n                return nil\n            } catch {\n                return nil\n            }\n            #else\n            _ = now\n            _ = respectKeychainPromptCooldown\n            return nil\n            #endif\n        }\n\n        @discardableResult\n        func syncFromClaudeKeychainWithoutPrompt(now: Date = Date()) -> Bool {\n            self.context.run {\n                #if os(macOS)\n                let mode = ClaudeOAuthKeychainPromptPreference.current()\n                guard ClaudeOAuthCredentialsStore.shouldAllowClaudeCodeKeychainAccess(mode: mode) else { return false }\n\n                if let data = ClaudeOAuthCredentialsStore.loadFromClaudeKeychainViaSecurityCLIIfEnabled(\n                    interaction: ProviderInteractionContext.current),\n                    !data.isEmpty\n                {\n                    if let creds = try? ClaudeOAuthCredentials.parse(data: data), !creds.isExpired {\n                        ClaudeOAuthCredentialsStore.writeMemoryCache(\n                            record: ClaudeOAuthCredentialRecord(\n                                credentials: creds,\n                                owner: .claudeCLI,\n                                source: .memoryCache),\n                            timestamp: now)\n                        ClaudeOAuthCredentialsStore.saveToCacheKeychain(data, owner: .claudeCLI)\n                        return true\n                    }\n                }\n\n                let fallbackPromptMode = ClaudeOAuthKeychainPromptPreference.securityFrameworkFallbackMode()\n                guard ClaudeOAuthCredentialsStore.shouldAllowClaudeCodeKeychainAccess(mode: fallbackPromptMode) else {\n                    return false\n                }\n\n                if ProviderInteractionContext.current == .background,\n                   !ClaudeOAuthKeychainAccessGate.shouldAllowPrompt(now: now)\n                {\n                    return false\n                }\n\n                #if DEBUG\n                let override = ClaudeOAuthCredentialsStore.taskClaudeKeychainOverrideStore?.data\n                    ?? ClaudeOAuthCredentialsStore.taskClaudeKeychainDataOverride\n                    ?? ClaudeOAuthCredentialsStore.claudeKeychainDataOverride\n                if let override,\n                   !override.isEmpty,\n                   let creds = try? ClaudeOAuthCredentials.parse(data: override),\n                   !creds.isExpired\n                {\n                    ClaudeOAuthCredentialsStore.saveClaudeKeychainFingerprint(\n                        ClaudeOAuthCredentialsStore.currentClaudeKeychainFingerprintWithoutPrompt())\n                    ClaudeOAuthCredentialsStore.writeMemoryCache(\n                        record: ClaudeOAuthCredentialRecord(\n                            credentials: creds,\n                            owner: .claudeCLI,\n                            source: .memoryCache),\n                        timestamp: now)\n                    ClaudeOAuthCredentialsStore.saveToCacheKeychain(override, owner: .claudeCLI)\n                    return true\n                }\n                #endif\n\n                if ClaudeOAuthCredentialsStore.shouldShowClaudeKeychainPreAlert() {\n                    return false\n                }\n\n                if let candidate = ClaudeOAuthCredentialsStore.claudeKeychainCandidatesWithoutPrompt(\n                    promptMode: fallbackPromptMode).first,\n                    let data = try? ClaudeOAuthCredentialsStore.loadClaudeKeychainData(\n                        candidate: candidate,\n                        allowKeychainPrompt: false),\n                    !data.isEmpty\n                {\n                    let fingerprint = ClaudeKeychainFingerprint(\n                        modifiedAt: candidate.modifiedAt.map { Int($0.timeIntervalSince1970) },\n                        createdAt: candidate.createdAt.map { Int($0.timeIntervalSince1970) },\n                        persistentRefHash: ClaudeOAuthCredentialsStore.sha256Prefix(candidate.persistentRef))\n\n                    if let creds = try? ClaudeOAuthCredentials.parse(data: data), !creds.isExpired {\n                        ClaudeOAuthCredentialsStore.saveClaudeKeychainFingerprint(fingerprint)\n                        ClaudeOAuthCredentialsStore.writeMemoryCache(\n                            record: ClaudeOAuthCredentialRecord(\n                                credentials: creds,\n                                owner: .claudeCLI,\n                                source: .memoryCache),\n                            timestamp: now)\n                        ClaudeOAuthCredentialsStore.saveToCacheKeychain(data, owner: .claudeCLI)\n                        return true\n                    }\n\n                    ClaudeOAuthCredentialsStore.saveClaudeKeychainFingerprint(fingerprint)\n                }\n\n                let legacyData = try? ClaudeOAuthCredentialsStore.loadClaudeKeychainLegacyData(\n                    allowKeychainPrompt: false,\n                    promptMode: fallbackPromptMode)\n                if let legacyData,\n                   !legacyData.isEmpty,\n                   let creds = try? ClaudeOAuthCredentials.parse(data: legacyData),\n                   !creds.isExpired\n                {\n                    ClaudeOAuthCredentialsStore.saveClaudeKeychainFingerprint(\n                        ClaudeOAuthCredentialsStore.currentClaudeKeychainFingerprintWithoutPrompt())\n                    ClaudeOAuthCredentialsStore.writeMemoryCache(\n                        record: ClaudeOAuthCredentialRecord(\n                            credentials: creds,\n                            owner: .claudeCLI,\n                            source: .memoryCache),\n                        timestamp: now)\n                    ClaudeOAuthCredentialsStore.saveToCacheKeychain(legacyData, owner: .claudeCLI)\n                    return true\n                }\n\n                return false\n                #else\n                _ = now\n                return false\n                #endif\n            }\n        }\n    }\n\n    private struct Refresher: Sendable {\n        let context: CollaboratorContext\n\n        func refreshAccessToken(\n            refreshToken: String,\n            existingScopes: [String],\n            existingRateLimitTier: String?) async throws -> ClaudeOAuthCredentials\n        {\n            try await self.context.run {\n                let newCredentials = try await self.refreshAccessTokenCore(\n                    refreshToken: refreshToken,\n                    existingScopes: existingScopes,\n                    existingRateLimitTier: existingRateLimitTier)\n\n                ClaudeOAuthCredentialsStore.saveRefreshedCredentialsToCache(newCredentials)\n                ClaudeOAuthCredentialsStore.writeMemoryCache(\n                    record: ClaudeOAuthCredentialRecord(\n                        credentials: newCredentials,\n                        owner: .codexbar,\n                        source: .memoryCache),\n                    timestamp: Date())\n                ClaudeOAuthRefreshFailureGate.recordSuccess()\n\n                return newCredentials\n            }\n        }\n\n        private func refreshAccessTokenCore(\n            refreshToken: String,\n            existingScopes: [String],\n            existingRateLimitTier: String?) async throws -> ClaudeOAuthCredentials\n        {\n            guard ClaudeOAuthRefreshFailureGate.shouldAttempt() else {\n                let status = ClaudeOAuthRefreshFailureGate.currentBlockStatus()\n                let message = switch status {\n                case .terminal:\n                    \"Claude OAuth refresh blocked until auth changes. \\(ClaudeOAuthCredentialsStore.reauthenticateHint)\"\n                case .transient:\n                    \"Claude OAuth refresh temporarily backed off due to prior failures; will retry automatically.\"\n                case nil:\n                    \"Claude OAuth refresh temporarily suppressed due to prior failures; will retry automatically.\"\n                }\n                throw ClaudeOAuthCredentialsError.refreshFailed(message)\n            }\n\n            guard let url = URL(string: ClaudeOAuthCredentialsStore.tokenRefreshEndpoint) else {\n                throw ClaudeOAuthCredentialsError.refreshFailed(\"Invalid token endpoint URL\")\n            }\n\n            var request = URLRequest(url: url)\n            request.httpMethod = \"POST\"\n            request.timeoutInterval = 30\n            request.setValue(\"application/x-www-form-urlencoded\", forHTTPHeaderField: \"Content-Type\")\n            request.setValue(\"application/json\", forHTTPHeaderField: \"Accept\")\n\n            var components = URLComponents()\n            components.queryItems = [\n                URLQueryItem(name: \"grant_type\", value: \"refresh_token\"),\n                URLQueryItem(name: \"refresh_token\", value: refreshToken),\n                URLQueryItem(name: \"client_id\", value: ClaudeOAuthCredentialsStore.oauthClientID),\n            ]\n            request.httpBody = (components.percentEncodedQuery ?? \"\").data(using: .utf8)\n\n            let (data, response) = try await URLSession.shared.data(for: request)\n\n            guard let http = response as? HTTPURLResponse else {\n                throw ClaudeOAuthCredentialsError.refreshFailed(\"Invalid response\")\n            }\n\n            guard http.statusCode == 200 else {\n                if let disposition = ClaudeOAuthCredentialsStore.refreshFailureDisposition(\n                    statusCode: http.statusCode,\n                    data: data)\n                {\n                    let oauthError = ClaudeOAuthCredentialsStore.extractOAuthErrorCode(from: data)\n                    ClaudeOAuthCredentialsStore.log.info(\n                        \"Claude OAuth refresh rejected\",\n                        metadata: [\n                            \"httpStatus\": \"\\(http.statusCode)\",\n                            \"oauthError\": oauthError ?? \"nil\",\n                            \"disposition\": disposition.rawValue,\n                        ])\n\n                    switch disposition {\n                    case .terminalInvalidGrant:\n                        ClaudeOAuthRefreshFailureGate.recordTerminalAuthFailure()\n                        Repository(context: self.context).invalidateCache()\n                        throw ClaudeOAuthCredentialsError.refreshFailed(\n                            \"HTTP \\(http.statusCode) invalid_grant. \\(ClaudeOAuthCredentialsStore.reauthenticateHint)\")\n                    case .transientBackoff:\n                        ClaudeOAuthRefreshFailureGate.recordTransientFailure()\n                        let suffix = oauthError.map { \" (\\($0))\" } ?? \"\"\n                        throw ClaudeOAuthCredentialsError.refreshFailed(\"HTTP \\(http.statusCode)\\(suffix)\")\n                    }\n                }\n                throw ClaudeOAuthCredentialsError.refreshFailed(\"HTTP \\(http.statusCode)\")\n            }\n\n            let tokenResponse = try JSONDecoder().decode(TokenRefreshResponse.self, from: data)\n            let expiresAt = Date(timeIntervalSinceNow: TimeInterval(tokenResponse.expiresIn))\n\n            return ClaudeOAuthCredentials(\n                accessToken: tokenResponse.accessToken,\n                refreshToken: tokenResponse.refreshToken ?? refreshToken,\n                expiresAt: expiresAt,\n                scopes: existingScopes,\n                rateLimitTier: existingRateLimitTier)\n        }\n    }\n\n    public static func load(\n        environment: [String: String] = ProcessInfo.processInfo.environment,\n        allowKeychainPrompt: Bool = true,\n        respectKeychainPromptCooldown: Bool = false) throws -> ClaudeOAuthCredentials\n    {\n        let context = self.currentCollaboratorContext()\n        return try Repository(context: context).load(\n            environment: environment,\n            allowKeychainPrompt: allowKeychainPrompt,\n            respectKeychainPromptCooldown: respectKeychainPromptCooldown)\n    }\n\n    public static func loadRecord(\n        environment: [String: String] = ProcessInfo.processInfo.environment,\n        allowKeychainPrompt: Bool = true,\n        respectKeychainPromptCooldown: Bool = false,\n        allowClaudeKeychainRepairWithoutPrompt: Bool = true) throws -> ClaudeOAuthCredentialRecord\n    {\n        let context = self.currentCollaboratorContext()\n        return try Repository(context: context).loadRecord(\n            environment: environment,\n            allowKeychainPrompt: allowKeychainPrompt,\n            respectKeychainPromptCooldown: respectKeychainPromptCooldown,\n            allowClaudeKeychainRepairWithoutPrompt: allowClaudeKeychainRepairWithoutPrompt)\n    }\n\n    /// Async version of load that handles expired tokens based on credential ownership.\n    /// - Claude CLI-owned credentials delegate refresh to Claude CLI.\n    /// - CodexBar-owned credentials refresh directly via token endpoint.\n    public static func loadWithAutoRefresh(\n        environment: [String: String] = ProcessInfo.processInfo.environment,\n        allowKeychainPrompt: Bool = true,\n        respectKeychainPromptCooldown: Bool = false) async throws -> ClaudeOAuthCredentials\n    {\n        let context = self.currentCollaboratorContext()\n        let repository = Repository(context: context)\n        let refresher = Refresher(context: context)\n        let record = try repository.loadRecord(\n            environment: environment,\n            allowKeychainPrompt: allowKeychainPrompt,\n            respectKeychainPromptCooldown: respectKeychainPromptCooldown,\n            allowClaudeKeychainRepairWithoutPrompt: true)\n        let credentials = record.credentials\n        let now = Date()\n        var expiryMetadata = credentials.diagnosticsMetadata(now: now)\n        expiryMetadata[\"source\"] = record.source.rawValue\n        expiryMetadata[\"owner\"] = record.owner.rawValue\n        expiryMetadata[\"allowKeychainPrompt\"] = \"\\(allowKeychainPrompt)\"\n        expiryMetadata[\"respectPromptCooldown\"] = \"\\(respectKeychainPromptCooldown)\"\n        expiryMetadata[\"readStrategy\"] = ClaudeOAuthKeychainReadStrategyPreference.current().rawValue\n\n        let isExpired: Bool = if let expiresAt = credentials.expiresAt {\n            now >= expiresAt\n        } else {\n            true\n        }\n\n        // If not expired, return as-is\n        guard isExpired else {\n            self.log.debug(\"Claude OAuth credentials loaded for usage\", metadata: expiryMetadata)\n            return credentials\n        }\n\n        self.log.info(\"Claude OAuth credentials considered expired\", metadata: expiryMetadata)\n\n        switch record.owner {\n        case .claudeCLI:\n            self.log.info(\n                \"Claude OAuth credentials expired; delegating refresh to Claude CLI\",\n                metadata: expiryMetadata)\n            throw ClaudeOAuthCredentialsError.refreshDelegatedToClaudeCLI\n        case .environment:\n            self.log.warning(\"Environment OAuth token expired and cannot be auto-refreshed\")\n            throw ClaudeOAuthCredentialsError.noRefreshToken\n        case .codexbar:\n            break\n        }\n\n        // Try to refresh if we have a refresh token.\n        guard let refreshToken = credentials.refreshToken, !refreshToken.isEmpty else {\n            self.log.warning(\"Token expired but no refresh token available\")\n            throw ClaudeOAuthCredentialsError.noRefreshToken\n        }\n        self.log.info(\"Access token expired, attempting auto-refresh\")\n\n        do {\n            let refreshed = try await refresher.refreshAccessToken(\n                refreshToken: refreshToken,\n                existingScopes: credentials.scopes,\n                existingRateLimitTier: credentials.rateLimitTier)\n            self.log.info(\"Token refresh successful, expires in \\(refreshed.expiresIn ?? 0) seconds\")\n            return refreshed\n        } catch {\n            self.log.error(\"Token refresh failed: \\(error.localizedDescription)\")\n            throw error\n        }\n    }\n\n    /// Save refreshed credentials to CodexBar's keychain cache\n    private static func saveRefreshedCredentialsToCache(_ credentials: ClaudeOAuthCredentials) {\n        var oauth: [String: Any] = [\n            \"accessToken\": credentials.accessToken,\n            \"expiresAt\": (credentials.expiresAt?.timeIntervalSince1970 ?? 0) * 1000,\n            \"scopes\": credentials.scopes,\n        ]\n\n        if let refreshToken = credentials.refreshToken {\n            oauth[\"refreshToken\"] = refreshToken\n        }\n        if let rateLimitTier = credentials.rateLimitTier {\n            oauth[\"rateLimitTier\"] = rateLimitTier\n        }\n\n        let oauthData: [String: Any] = [\"claudeAiOauth\": oauth]\n\n        guard let jsonData = try? JSONSerialization.data(withJSONObject: oauthData) else {\n            self.log.error(\"Failed to serialize refreshed credentials for cache\")\n            return\n        }\n\n        self.saveToCacheKeychain(jsonData, owner: .codexbar)\n        self.log.debug(\"Saved refreshed credentials to CodexBar keychain cache\")\n    }\n\n    /// Response from the OAuth token refresh endpoint\n    private struct TokenRefreshResponse: Decodable {\n        let accessToken: String\n        let refreshToken: String?\n        let expiresIn: Int\n        let tokenType: String?\n\n        enum CodingKeys: String, CodingKey {\n            case accessToken = \"access_token\"\n            case refreshToken = \"refresh_token\"\n            case expiresIn = \"expires_in\"\n            case tokenType = \"token_type\"\n        }\n    }\n\n    public static func loadFromFile() throws -> Data {\n        let url = self.credentialsFileURL()\n        do {\n            return try Data(contentsOf: url)\n        } catch {\n            if (error as NSError).code == NSFileReadNoSuchFileError {\n                throw ClaudeOAuthCredentialsError.notFound\n            }\n            throw ClaudeOAuthCredentialsError.readFailed(error.localizedDescription)\n        }\n    }\n\n    @discardableResult\n    public static func invalidateCacheIfCredentialsFileChanged() -> Bool {\n        Repository(context: self.currentCollaboratorContext()).invalidateCacheIfCredentialsFileChanged()\n    }\n\n    /// Invalidate the credentials cache (call after login/logout)\n    public static func invalidateCache() {\n        Repository(context: self.currentCollaboratorContext()).invalidateCache()\n    }\n\n    /// Check if CodexBar has cached credentials (in memory or keychain cache)\n    public static func hasCachedCredentials(environment: [String: String] = ProcessInfo.processInfo\n        .environment) -> Bool\n    {\n        Repository(context: self.currentCollaboratorContext()).hasCachedCredentials(environment: environment)\n    }\n\n    public static func hasClaudeKeychainCredentialsWithoutPrompt() -> Bool {\n        Repository(context: self.currentCollaboratorContext()).hasClaudeKeychainCredentialsWithoutPrompt()\n    }\n\n    private static func shouldCheckClaudeKeychainChange(now: Date = Date()) -> Bool {\n        #if DEBUG\n        // Unit tests can supply TaskLocal overrides for the Claude keychain data/fingerprint. Those tests often run\n        // concurrently with other suites, so the global throttle becomes nondeterministic. When an override is\n        // present, bypass the throttle so test expectations don't depend on unrelated activity.\n        if self.taskClaudeKeychainOverrideStore != nil || self.taskClaudeKeychainFingerprintOverride != nil\n            || self.claudeKeychainFingerprintOverride != nil { return true }\n        #endif\n\n        self.claudeKeychainChangeCheckLock.lock()\n        defer { self.claudeKeychainChangeCheckLock.unlock() }\n        if let last = self.lastClaudeKeychainChangeCheckAt,\n           now.timeIntervalSince(last) < self.claudeKeychainChangeCheckMinimumInterval\n        {\n            return false\n        }\n        self.lastClaudeKeychainChangeCheckAt = now\n        return true\n    }\n\n    private static func loadClaudeKeychainFingerprint() -> ClaudeKeychainFingerprint? {\n        #if DEBUG\n        if let store = taskClaudeKeychainFingerprintStoreOverride {\n            return store.fingerprint\n        }\n        #endif\n        // Proactively remove the legacy V1 key (it included the keychain account string, which can be identifying).\n        UserDefaults.standard.removeObject(forKey: self.claudeKeychainFingerprintLegacyKey)\n\n        guard let data = UserDefaults.standard.data(forKey: self.claudeKeychainFingerprintKey) else {\n            return nil\n        }\n        return try? JSONDecoder().decode(ClaudeKeychainFingerprint.self, from: data)\n    }\n\n    private static func saveClaudeKeychainFingerprint(_ fingerprint: ClaudeKeychainFingerprint?) {\n        #if DEBUG\n        if let store = taskClaudeKeychainFingerprintStoreOverride {\n            store.fingerprint = fingerprint\n            return\n        }\n        #endif\n        // Proactively remove the legacy V1 key (it included the keychain account string, which can be identifying).\n        UserDefaults.standard.removeObject(forKey: self.claudeKeychainFingerprintLegacyKey)\n\n        guard let fingerprint else {\n            UserDefaults.standard.removeObject(forKey: self.claudeKeychainFingerprintKey)\n            return\n        }\n        if let data = try? JSONEncoder().encode(fingerprint) {\n            UserDefaults.standard.set(data, forKey: self.claudeKeychainFingerprintKey)\n        }\n    }\n\n    private static func currentClaudeKeychainFingerprintWithoutPrompt() -> ClaudeKeychainFingerprint? {\n        let mode = ClaudeOAuthKeychainPromptPreference.current()\n        guard self.shouldAllowClaudeCodeKeychainAccess(mode: mode) else { return nil }\n        #if DEBUG\n        if let store = taskClaudeKeychainOverrideStore { return store.fingerprint }\n        if let override = taskClaudeKeychainFingerprintOverride ?? self\n            .claudeKeychainFingerprintOverride { return override }\n        #endif\n        #if os(macOS)\n        let newest: ClaudeKeychainCandidate? = self.claudeKeychainCandidatesWithoutPrompt().first\n            ?? self.claudeKeychainLegacyCandidateWithoutPrompt()\n        guard let newest else { return nil }\n\n        let modifiedAt = newest.modifiedAt.map { Int($0.timeIntervalSince1970) }\n        let createdAt = newest.createdAt.map { Int($0.timeIntervalSince1970) }\n        let persistentRefHash = Self.sha256Prefix(newest.persistentRef)\n        return ClaudeKeychainFingerprint(\n            modifiedAt: modifiedAt,\n            createdAt: createdAt,\n            persistentRefHash: persistentRefHash)\n        #else\n        return nil\n        #endif\n    }\n\n    static func currentClaudeKeychainFingerprintWithoutPromptForAuthGate() -> ClaudeKeychainFingerprint? {\n        self.currentClaudeKeychainFingerprintWithoutPrompt()\n    }\n\n    static func currentCredentialsFileFingerprintWithoutPromptForAuthGate() -> String? {\n        guard let fingerprint = self.currentFileFingerprint() else { return nil }\n        let modifiedAt = fingerprint.modifiedAtMs ?? 0\n        return \"\\(modifiedAt):\\(fingerprint.size)\"\n    }\n\n    private static func loadFromClaudeKeychainNonInteractive() throws -> Data? {\n        #if os(macOS)\n        let fallbackPromptMode = ClaudeOAuthKeychainPromptPreference.securityFrameworkFallbackMode()\n        if let data = self.loadFromClaudeKeychainViaSecurityCLIIfEnabled(\n            interaction: ProviderInteractionContext.current)\n        {\n            return data\n        }\n\n        // For experimental strategy, enforce stored prompt policy before any Security.framework fallback probes.\n        guard self.shouldAllowClaudeCodeKeychainAccess(mode: fallbackPromptMode) else { return nil }\n\n        #if DEBUG\n        if let store = taskClaudeKeychainOverrideStore { return store.data }\n        if let override = taskClaudeKeychainDataOverride ?? self.claudeKeychainDataOverride { return override }\n        #endif\n\n        // Keep semantics aligned with fingerprinting: if there are multiple entries, we only ever consult the newest\n        // candidate (same as currentClaudeKeychainFingerprintWithoutPrompt()) to avoid syncing from a different item.\n        let candidates = self.claudeKeychainCandidatesWithoutPrompt(promptMode: fallbackPromptMode)\n        if let newest = candidates.first {\n            if let data = try self.loadClaudeKeychainData(candidate: newest, allowKeychainPrompt: false),\n               !data.isEmpty\n            {\n                return data\n            }\n            return nil\n        }\n\n        let legacyData = try self.loadClaudeKeychainLegacyData(\n            allowKeychainPrompt: false,\n            promptMode: fallbackPromptMode)\n        if let legacyData, !legacyData.isEmpty { return legacyData }\n        return nil\n        #else\n        return nil\n        #endif\n    }\n\n    public static func loadFromClaudeKeychain() throws -> Data {\n        guard self.shouldAllowClaudeCodeKeychainAccess(mode: ClaudeOAuthKeychainPromptPreference.current()) else {\n            throw ClaudeOAuthCredentialsError.notFound\n        }\n        #if DEBUG\n        if let store = taskClaudeKeychainOverrideStore, let override = store.data { return override }\n        if let override = taskClaudeKeychainDataOverride ?? self.claudeKeychainDataOverride { return override }\n        #endif\n        if let data = self.loadFromClaudeKeychainViaSecurityCLIIfEnabled(\n            interaction: ProviderInteractionContext.current)\n        {\n            return data\n        }\n        if self.shouldPreferSecurityCLIKeychainRead() {\n            let fallbackPromptMode = ClaudeOAuthKeychainPromptPreference.securityFrameworkFallbackMode()\n            let fallbackDecision = self.securityFrameworkFallbackPromptDecision(\n                promptMode: fallbackPromptMode,\n                allowKeychainPrompt: true,\n                respectKeychainPromptCooldown: false)\n            self.log.debug(\n                \"Claude keychain Security.framework fallback prompt policy evaluated\",\n                metadata: [\n                    \"reader\": \"securityFrameworkFallback\",\n                    \"fallbackPromptMode\": fallbackPromptMode.rawValue,\n                    \"fallbackPromptAllowed\": \"\\(fallbackDecision.allowed)\",\n                    \"fallbackBlockedReason\": fallbackDecision.blockedReason ?? \"none\",\n                ])\n            guard fallbackDecision.allowed else {\n                throw ClaudeOAuthCredentialsError.notFound\n            }\n            return try self.loadFromClaudeKeychainUsingSecurityFramework(\n                promptMode: fallbackPromptMode,\n                allowKeychainPrompt: true)\n        }\n        return try self.loadFromClaudeKeychainUsingSecurityFramework()\n    }\n\n    /// Legacy alias for backward compatibility\n    public static func loadFromKeychain() throws -> Data {\n        try self.loadFromClaudeKeychain()\n    }\n\n    private static func loadFromClaudeKeychainUsingSecurityFramework(\n        promptMode: ClaudeOAuthKeychainPromptMode = ClaudeOAuthKeychainPromptPreference.current(),\n        allowKeychainPrompt: Bool = true) throws -> Data\n    {\n        #if DEBUG\n        if let store = taskClaudeKeychainOverrideStore, let override = store.data { return override }\n        if let override = taskClaudeKeychainDataOverride ?? self.claudeKeychainDataOverride { return override }\n        #endif\n        #if os(macOS)\n        let candidates = self.claudeKeychainCandidatesWithoutPrompt(promptMode: promptMode)\n        if let newest = candidates.first {\n            do {\n                if let data = try self.loadClaudeKeychainData(\n                    candidate: newest,\n                    allowKeychainPrompt: allowKeychainPrompt,\n                    promptMode: promptMode),\n                    !data.isEmpty\n                {\n                    // Store fingerprint after a successful interactive read so we don't immediately try to\n                    // \"sync\" in the background (which can still show UI on some systems).\n                    let modifiedAt = newest.modifiedAt.map { Int($0.timeIntervalSince1970) }\n                    let createdAt = newest.createdAt.map { Int($0.timeIntervalSince1970) }\n                    let persistentRefHash = Self.sha256Prefix(newest.persistentRef)\n                    self.saveClaudeKeychainFingerprint(\n                        ClaudeKeychainFingerprint(\n                            modifiedAt: modifiedAt,\n                            createdAt: createdAt,\n                            persistentRefHash: persistentRefHash))\n                    return data\n                }\n            } catch let error as ClaudeOAuthCredentialsError {\n                if case .keychainError = error {\n                    ClaudeOAuthKeychainAccessGate.recordDenied()\n                }\n                throw error\n            }\n        }\n\n        // Fallback: legacy query (may pick an arbitrary duplicate).\n        do {\n            if let data = try self.loadClaudeKeychainLegacyData(\n                allowKeychainPrompt: allowKeychainPrompt,\n                promptMode: promptMode),\n                !data.isEmpty\n            {\n                // Same as above: store fingerprint after interactive read to avoid background \"sync\" reads.\n                self.saveClaudeKeychainFingerprint(self.currentClaudeKeychainFingerprintWithoutPrompt())\n                return data\n            }\n        } catch let error as ClaudeOAuthCredentialsError {\n            if case .keychainError = error {\n                ClaudeOAuthKeychainAccessGate.recordDenied()\n            }\n            throw error\n        }\n        throw ClaudeOAuthCredentialsError.notFound\n        #else\n        throw ClaudeOAuthCredentialsError.notFound\n        #endif\n    }\n\n    #if os(macOS)\n    private struct ClaudeKeychainCandidate {\n        let persistentRef: Data\n        let account: String?\n        let modifiedAt: Date?\n        let createdAt: Date?\n    }\n\n    private static func claudeKeychainCandidatesWithoutPrompt(\n        promptMode: ClaudeOAuthKeychainPromptMode = ClaudeOAuthKeychainPromptPreference\n            .current()) -> [ClaudeKeychainCandidate]\n    {\n        guard self.shouldAllowClaudeCodeKeychainAccess(mode: promptMode) else { return [] }\n        if self.isPromptPolicyApplicable,\n           ProviderInteractionContext.current == .background,\n           !ClaudeOAuthKeychainAccessGate.shouldAllowPrompt() { return [] }\n        var query: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: self.claudeKeychainService,\n            kSecMatchLimit as String: kSecMatchLimitAll,\n            kSecReturnAttributes as String: true,\n            kSecReturnPersistentRef as String: true,\n        ]\n        KeychainNoUIQuery.apply(to: &query)\n\n        let (status, result, durationMs) = ClaudeOAuthKeychainQueryTiming.copyMatching(query)\n        if ClaudeOAuthKeychainQueryTiming\n            .backoffIfSlowNoUIQuery(durationMs, self.claudeKeychainService, self.log) { return [] }\n        if status == errSecUserCanceled || status == errSecAuthFailed || status == errSecNoAccessForItem {\n            ClaudeOAuthKeychainAccessGate.recordDenied()\n        }\n        guard status == errSecSuccess else { return [] }\n        guard let rows = result as? [[String: Any]], !rows.isEmpty else { return [] }\n\n        let candidates: [ClaudeKeychainCandidate] = rows.compactMap { row in\n            guard let persistentRef = row[kSecValuePersistentRef as String] as? Data else { return nil }\n            return ClaudeKeychainCandidate(\n                persistentRef: persistentRef,\n                account: row[kSecAttrAccount as String] as? String,\n                modifiedAt: row[kSecAttrModificationDate as String] as? Date,\n                createdAt: row[kSecAttrCreationDate as String] as? Date)\n        }\n\n        return candidates.sorted { lhs, rhs in\n            let lhsDate = lhs.modifiedAt ?? lhs.createdAt ?? Date.distantPast\n            let rhsDate = rhs.modifiedAt ?? rhs.createdAt ?? Date.distantPast\n            return lhsDate > rhsDate\n        }\n    }\n\n    private static func claudeKeychainLegacyCandidateWithoutPrompt(\n        promptMode: ClaudeOAuthKeychainPromptMode = ClaudeOAuthKeychainPromptPreference\n            .current()) -> ClaudeKeychainCandidate?\n    {\n        guard self.shouldAllowClaudeCodeKeychainAccess(mode: promptMode) else { return nil }\n        if self.isPromptPolicyApplicable,\n           ProviderInteractionContext.current == .background,\n           !ClaudeOAuthKeychainAccessGate.shouldAllowPrompt() { return nil }\n        var query: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: self.claudeKeychainService,\n            kSecMatchLimit as String: kSecMatchLimitOne,\n            kSecReturnAttributes as String: true,\n            kSecReturnPersistentRef as String: true,\n        ]\n        KeychainNoUIQuery.apply(to: &query)\n\n        let (status, result, durationMs) = ClaudeOAuthKeychainQueryTiming.copyMatching(query)\n        if ClaudeOAuthKeychainQueryTiming\n            .backoffIfSlowNoUIQuery(durationMs, self.claudeKeychainService, self.log) { return nil }\n        if status == errSecUserCanceled || status == errSecAuthFailed || status == errSecNoAccessForItem {\n            ClaudeOAuthKeychainAccessGate.recordDenied()\n        }\n        guard status == errSecSuccess else { return nil }\n        guard let row = result as? [String: Any] else { return nil }\n        guard let persistentRef = row[kSecValuePersistentRef as String] as? Data else { return nil }\n        return ClaudeKeychainCandidate(\n            persistentRef: persistentRef,\n            account: row[kSecAttrAccount as String] as? String,\n            modifiedAt: row[kSecAttrModificationDate as String] as? Date,\n            createdAt: row[kSecAttrCreationDate as String] as? Date)\n    }\n\n    private static func loadClaudeKeychainData(\n        candidate: ClaudeKeychainCandidate,\n        allowKeychainPrompt: Bool,\n        promptMode: ClaudeOAuthKeychainPromptMode = ClaudeOAuthKeychainPromptPreference.current()) throws -> Data?\n    {\n        guard self.shouldAllowClaudeCodeKeychainAccess(mode: promptMode) else { return nil }\n        self.log.debug(\n            \"Claude keychain data read start\",\n            metadata: [\n                \"service\": self.claudeKeychainService,\n                \"interactive\": \"\\(allowKeychainPrompt)\",\n                \"process\": ProcessInfo.processInfo.processName,\n            ])\n\n        var query: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecValuePersistentRef as String: candidate.persistentRef,\n            kSecMatchLimit as String: kSecMatchLimitOne,\n            kSecReturnData as String: true,\n        ]\n\n        if !allowKeychainPrompt {\n            KeychainNoUIQuery.apply(to: &query)\n        }\n\n        var result: AnyObject?\n        let startedAtNs = DispatchTime.now().uptimeNanoseconds\n        let status = SecItemCopyMatching(query as CFDictionary, &result)\n        let durationMs = Double(DispatchTime.now().uptimeNanoseconds - startedAtNs) / 1_000_000.0\n        self.log.debug(\n            \"Claude keychain data read result\",\n            metadata: [\n                \"service\": self.claudeKeychainService,\n                \"interactive\": \"\\(allowKeychainPrompt)\",\n                \"status\": \"\\(status)\",\n                \"duration_ms\": String(format: \"%.2f\", durationMs),\n                \"process\": ProcessInfo.processInfo.processName,\n            ])\n        switch status {\n        case errSecSuccess:\n            if let data = result as? Data {\n                return data\n            }\n            return nil\n        case errSecItemNotFound:\n            return nil\n        case errSecInteractionNotAllowed:\n            if allowKeychainPrompt {\n                ClaudeOAuthKeychainAccessGate.recordDenied()\n                throw ClaudeOAuthCredentialsError.keychainError(Int(status))\n            }\n            return nil\n        case errSecUserCanceled, errSecAuthFailed:\n            ClaudeOAuthKeychainAccessGate.recordDenied()\n            throw ClaudeOAuthCredentialsError.keychainError(Int(status))\n        case errSecNoAccessForItem:\n            ClaudeOAuthKeychainAccessGate.recordDenied()\n            throw ClaudeOAuthCredentialsError.keychainError(Int(status))\n        default:\n            throw ClaudeOAuthCredentialsError.keychainError(Int(status))\n        }\n    }\n\n    private static func loadClaudeKeychainLegacyData(\n        allowKeychainPrompt: Bool,\n        promptMode: ClaudeOAuthKeychainPromptMode = ClaudeOAuthKeychainPromptPreference.current()) throws -> Data?\n    {\n        guard self.shouldAllowClaudeCodeKeychainAccess(mode: promptMode) else { return nil }\n        self.log.debug(\n            \"Claude keychain legacy data read start\",\n            metadata: [\n                \"service\": self.claudeKeychainService,\n                \"interactive\": \"\\(allowKeychainPrompt)\",\n                \"process\": ProcessInfo.processInfo.processName,\n            ])\n\n        var query: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: self.claudeKeychainService,\n            kSecMatchLimit as String: kSecMatchLimitOne,\n            kSecReturnData as String: true,\n        ]\n\n        if !allowKeychainPrompt {\n            KeychainNoUIQuery.apply(to: &query)\n        }\n\n        var result: AnyObject?\n        let startedAtNs = DispatchTime.now().uptimeNanoseconds\n        let status = SecItemCopyMatching(query as CFDictionary, &result)\n        let durationMs = Double(DispatchTime.now().uptimeNanoseconds - startedAtNs) / 1_000_000.0\n        self.log.debug(\n            \"Claude keychain legacy data read result\",\n            metadata: [\n                \"service\": self.claudeKeychainService,\n                \"interactive\": \"\\(allowKeychainPrompt)\",\n                \"status\": \"\\(status)\",\n                \"duration_ms\": String(format: \"%.2f\", durationMs),\n                \"process\": ProcessInfo.processInfo.processName,\n            ])\n        switch status {\n        case errSecSuccess:\n            return result as? Data\n        case errSecItemNotFound:\n            return nil\n        case errSecInteractionNotAllowed:\n            if allowKeychainPrompt {\n                ClaudeOAuthKeychainAccessGate.recordDenied()\n                throw ClaudeOAuthCredentialsError.keychainError(Int(status))\n            }\n            return nil\n        case errSecUserCanceled, errSecAuthFailed:\n            ClaudeOAuthKeychainAccessGate.recordDenied()\n            throw ClaudeOAuthCredentialsError.keychainError(Int(status))\n        case errSecNoAccessForItem:\n            ClaudeOAuthKeychainAccessGate.recordDenied()\n            throw ClaudeOAuthCredentialsError.keychainError(Int(status))\n        default:\n            throw ClaudeOAuthCredentialsError.keychainError(Int(status))\n        }\n    }\n    #endif\n\n    private static func loadFromEnvironment(_ environment: [String: String])\n        -> ClaudeOAuthCredentials?\n    {\n        guard\n            let token = environment[self.environmentTokenKey]?.trimmingCharacters(\n                in: .whitespacesAndNewlines),\n            !token.isEmpty\n        else {\n            return nil\n        }\n\n        let scopes: [String] = {\n            guard let raw = environment[self.environmentScopesKey] else { return [\"user:profile\"] }\n            let parsed =\n                raw\n                    .split(separator: \",\")\n                    .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }\n                    .filter { !$0.isEmpty }\n            return parsed.isEmpty ? [\"user:profile\"] : parsed\n        }()\n\n        return ClaudeOAuthCredentials(\n            accessToken: token,\n            refreshToken: nil,\n            expiresAt: Date.distantFuture,\n            scopes: scopes,\n            rateLimitTier: nil)\n    }\n\n    static func setCredentialsURLOverrideForTesting(_ url: URL?) {\n        self.credentialsURLOverride = url\n    }\n\n    #if DEBUG\n    public static func withCredentialsURLOverrideForTesting<T>(_ url: URL?, operation: () throws -> T) rethrows -> T {\n        try self.$taskCredentialsURLOverride.withValue(url) {\n            try operation()\n        }\n    }\n\n    public static func withCredentialsURLOverrideForTesting<T>(_ url: URL?, operation: () async throws -> T)\n    async rethrows -> T {\n        try await self.$taskCredentialsURLOverride.withValue(url) {\n            try await operation()\n        }\n    }\n\n    public static var currentCredentialsURLOverrideForTesting: URL? {\n        self.taskCredentialsURLOverride\n    }\n    #endif\n\n    private static func saveToCacheKeychain(_ data: Data, owner: ClaudeOAuthCredentialOwner? = nil) {\n        let entry = CacheEntry(data: data, storedAt: Date(), owner: owner)\n        KeychainCacheStore.store(key: self.cacheKey, entry: entry)\n    }\n\n    private static func clearCacheKeychain() {\n        KeychainCacheStore.clear(key: self.cacheKey)\n    }\n\n    private static var keychainAccessAllowed: Bool {\n        #if DEBUG\n        if let override = self.taskKeychainAccessOverride { return !override }\n        #endif\n        return !KeychainAccessGate.isDisabled\n    }\n\n    private static var isPromptPolicyApplicable: Bool {\n        ClaudeOAuthKeychainPromptPreference.isApplicable()\n    }\n\n    private static func securityFrameworkFallbackPromptDecision(\n        promptMode: ClaudeOAuthKeychainPromptMode,\n        allowKeychainPrompt: Bool,\n        respectKeychainPromptCooldown: Bool) -> (allowed: Bool, blockedReason: String?)\n    {\n        guard allowKeychainPrompt else {\n            return (allowed: false, blockedReason: \"allowKeychainPromptFalse\")\n        }\n        guard self.shouldAllowClaudeCodeKeychainAccess(mode: promptMode) else {\n            return (allowed: false, blockedReason: self.fallbackBlockedReason(promptMode: promptMode))\n        }\n        if respectKeychainPromptCooldown,\n           !ClaudeOAuthKeychainAccessGate.shouldAllowPrompt()\n        {\n            return (allowed: false, blockedReason: \"cooldown\")\n        }\n        return (allowed: true, blockedReason: nil)\n    }\n\n    private static func fallbackBlockedReason(promptMode: ClaudeOAuthKeychainPromptMode) -> String {\n        if !self.keychainAccessAllowed { return \"keychainDisabled\" }\n        switch promptMode {\n        case .never:\n            return \"never\"\n        case .onlyOnUserAction:\n            return \"onlyOnUserAction-background\"\n        case .always:\n            return \"disallowed\"\n        }\n    }\n\n    private static func shouldAllowClaudeCodeKeychainAccess(\n        mode: ClaudeOAuthKeychainPromptMode = ClaudeOAuthKeychainPromptPreference.current()) -> Bool\n    {\n        guard self.keychainAccessAllowed else { return false }\n        switch mode {\n        case .never: return false\n        case .onlyOnUserAction:\n            return ProviderInteractionContext.current == .userInitiated || self.allowBackgroundPromptBootstrap\n        case .always: return true\n        }\n    }\n\n    static func preferredClaudeKeychainAccountForSecurityCLIRead(\n        interaction: ProviderInteraction = ProviderInteractionContext.current) -> String?\n    {\n        // Keep the experimental background path fully on /usr/bin/security by default.\n        // Account pinning requires Security.framework candidate probing, so only allow it on explicit user actions.\n        guard interaction == .userInitiated else { return nil }\n        #if DEBUG\n        if let override = self.taskSecurityCLIReadAccountOverride { return override }\n        #endif\n        #if os(macOS)\n        let mode = ClaudeOAuthKeychainPromptPreference.current()\n        guard self.shouldAllowClaudeCodeKeychainAccess(mode: mode) else { return nil }\n        // Keep experimental mode prompt-safe: avoid Security.framework candidate probes when preflight says\n        // interaction is likely.\n        if self.shouldShowClaudeKeychainPreAlert() {\n            return nil\n        }\n        guard let account = self.claudeKeychainCandidatesWithoutPrompt(promptMode: mode).first?.account,\n              !account.isEmpty\n        else {\n            return nil\n        }\n        return account\n        #else\n        return nil\n        #endif\n    }\n\n    private static func credentialsFileURL() -> URL {\n        #if DEBUG\n        if let override = self.taskCredentialsURLOverride { return override }\n        #endif\n        return self.credentialsURLOverride ?? self.defaultCredentialsURL()\n    }\n\n    private static func loadFileFingerprint() -> CredentialsFileFingerprint? {\n        #if DEBUG\n        if let store = self.taskCredentialsFileFingerprintStoreOverride { return store.load() }\n        #endif\n        guard let data = UserDefaults.standard.data(forKey: self.fileFingerprintKey) else {\n            return nil\n        }\n        return try? JSONDecoder().decode(CredentialsFileFingerprint.self, from: data)\n    }\n\n    private static func saveFileFingerprint(_ fingerprint: CredentialsFileFingerprint?) {\n        #if DEBUG\n        if let store = self.taskCredentialsFileFingerprintStoreOverride { store.save(fingerprint); return }\n        #endif\n        guard let fingerprint else {\n            UserDefaults.standard.removeObject(forKey: self.fileFingerprintKey)\n            return\n        }\n        if let data = try? JSONEncoder().encode(fingerprint) {\n            UserDefaults.standard.set(data, forKey: self.fileFingerprintKey)\n        }\n    }\n\n    private static func currentFileFingerprint() -> CredentialsFileFingerprint? {\n        let url = self.credentialsFileURL()\n        guard let attrs = try? FileManager.default.attributesOfItem(atPath: url.path) else {\n            return nil\n        }\n        let size = (attrs[.size] as? NSNumber)?.intValue ?? 0\n        let modifiedAtMs = (attrs[.modificationDate] as? Date).map { Int($0.timeIntervalSince1970 * 1000) }\n        return CredentialsFileFingerprint(modifiedAtMs: modifiedAtMs, size: size)\n    }\n\n    #if DEBUG\n    static func _resetCredentialsFileTrackingForTesting() {\n        if let store = self.taskCredentialsFileFingerprintStoreOverride { store.save(nil); return }\n        UserDefaults.standard.removeObject(forKey: self.fileFingerprintKey)\n    }\n\n    static func _resetClaudeKeychainChangeTrackingForTesting() {\n        UserDefaults.standard.removeObject(forKey: self.claudeKeychainFingerprintKey)\n        UserDefaults.standard.removeObject(forKey: self.claudeKeychainFingerprintLegacyKey)\n        self.setClaudeKeychainDataOverrideForTesting(nil)\n        self.setClaudeKeychainFingerprintOverrideForTesting(nil)\n        self.claudeKeychainChangeCheckLock.lock()\n        self.lastClaudeKeychainChangeCheckAt = nil\n        self.claudeKeychainChangeCheckLock.unlock()\n    }\n\n    static func _resetClaudeKeychainChangeThrottleForTesting() {\n        self.claudeKeychainChangeCheckLock.lock()\n        self.lastClaudeKeychainChangeCheckAt = nil\n        self.claudeKeychainChangeCheckLock.unlock()\n    }\n    #endif\n\n    private static func defaultCredentialsURL() -> URL {\n        let home = FileManager.default.homeDirectoryForCurrentUser\n        return home.appendingPathComponent(self.credentialsPath)\n    }\n}\n\n// swiftlint:enable type_body_length\n\nextension ClaudeOAuthCredentialsStore {\n    /// After delegated Claude CLI refresh, re-load the Claude keychain entry without prompting and sync it into\n    /// CodexBar's caches. This is used to avoid triggering a second OS keychain dialog during the OAuth retry.\n    @discardableResult\n    static func syncFromClaudeKeychainWithoutPrompt(now: Date = Date()) -> Bool {\n        Recovery(context: self.currentCollaboratorContext()).syncFromClaudeKeychainWithoutPrompt(now: now)\n    }\n\n    private static func shouldShowClaudeKeychainPreAlert() -> Bool {\n        let mode = ClaudeOAuthKeychainPromptPreference.current()\n        guard self.shouldAllowClaudeCodeKeychainAccess(mode: mode) else { return false }\n        return switch KeychainAccessPreflight.checkGenericPassword(service: self.claudeKeychainService, account: nil) {\n        case .interactionRequired:\n            true\n        case .failure:\n            // If preflight fails, we can't be sure whether interaction is required (or if the preflight itself\n            // is impacted by a misbehaving Keychain configuration). Be conservative and show the pre-alert.\n            true\n        case .allowed, .notFound:\n            false\n        }\n    }\n\n    /// Refresh the access token using a refresh token.\n    /// Updates CodexBar's keychain cache with the new credentials.\n    public static func refreshAccessToken(\n        refreshToken: String,\n        existingScopes: [String],\n        existingRateLimitTier: String?) async throws -> ClaudeOAuthCredentials\n    {\n        try await Refresher(context: self.currentCollaboratorContext()).refreshAccessToken(\n            refreshToken: refreshToken,\n            existingScopes: existingScopes,\n            existingRateLimitTier: existingRateLimitTier)\n    }\n\n    private enum RefreshFailureDisposition: String {\n        case terminalInvalidGrant\n        case transientBackoff\n    }\n\n    private static func extractOAuthErrorCode(from data: Data) -> String? {\n        guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {\n            return nil\n        }\n        return json[\"error\"] as? String\n    }\n\n    private static func refreshFailureDisposition(statusCode: Int, data: Data) -> RefreshFailureDisposition? {\n        guard statusCode == 400 || statusCode == 401 else { return nil }\n        if let error = self.extractOAuthErrorCode(from: data)?.lowercased(), error == \"invalid_grant\" {\n            return .terminalInvalidGrant\n        }\n        return .transientBackoff\n    }\n\n    #if DEBUG\n    static func extractOAuthErrorCodeForTesting(from data: Data) -> String? {\n        self.extractOAuthErrorCode(from: data)\n    }\n\n    static func refreshFailureDispositionForTesting(statusCode: Int, data: Data) -> String? {\n        self.refreshFailureDisposition(statusCode: statusCode, data: data)?.rawValue\n    }\n    #endif\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthDelegatedRefreshCoordinator.swift",
    "content": "import Foundation\n\npublic enum ClaudeOAuthDelegatedRefreshCoordinator {\n    private final class AttemptStateStorage: @unchecked Sendable {\n        let lock = NSLock()\n        let persistsCooldown: Bool\n        var hasLoadedState = false\n        var lastAttemptAt: Date?\n        var lastCooldownInterval: TimeInterval?\n        var inFlightAttemptID: UInt64?\n        var inFlightTask: Task<Outcome, Never>?\n        var nextAttemptID: UInt64 = 0\n\n        init(persistsCooldown: Bool) {\n            self.persistsCooldown = persistsCooldown\n        }\n    }\n\n    public enum Outcome: Sendable, Equatable {\n        case skippedByCooldown\n        case cliUnavailable\n        case attemptedSucceeded\n        case attemptedFailed(String)\n    }\n\n    private static let log = CodexBarLog.logger(LogCategories.claudeUsage)\n    private static let cooldownDefaultsKey = \"claudeOAuthDelegatedRefreshLastAttemptAtV1\"\n    private static let cooldownIntervalDefaultsKey = \"claudeOAuthDelegatedRefreshCooldownIntervalSecondsV1\"\n    private static let defaultCooldownInterval: TimeInterval = 60 * 5\n    private static let shortCooldownInterval: TimeInterval = 20\n\n    private static let sharedState = AttemptStateStorage(persistsCooldown: true)\n\n    public static func attempt(\n        now: Date = Date(),\n        timeout: TimeInterval = 8,\n        environment: [String: String] = ProcessInfo.processInfo.environment) async -> Outcome\n    {\n        if Task.isCancelled {\n            return .attemptedFailed(\"Cancelled.\")\n        }\n\n        switch self.inFlightDecision(now: now, timeout: timeout, environment: environment) {\n        case let .join(task):\n            return await task.value\n        case let .start(id, task, state):\n            let outcome = await task.value\n            self.clearInFlightTaskIfStillCurrent(id: id, state: state)\n            return outcome\n        }\n    }\n\n    private enum InFlightDecision {\n        case join(Task<Outcome, Never>)\n        case start(UInt64, Task<Outcome, Never>, AttemptStateStorage)\n    }\n\n    private struct AttemptConfiguration: Sendable {\n        let environment: [String: String]\n        let readStrategy: ClaudeOAuthKeychainReadStrategy\n        let keychainAccessDisabled: Bool\n        #if DEBUG\n        let cliAvailableOverride: Bool?\n        let touchAuthPathOverride: (@Sendable (TimeInterval, [String: String]) async throws -> Void)?\n        let keychainFingerprintOverride: (@Sendable () -> ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint?)?\n        #endif\n    }\n\n    private static func inFlightDecision(\n        now: Date,\n        timeout: TimeInterval,\n        environment: [String: String]) -> InFlightDecision\n    {\n        let state = self.currentStateStorage\n        state.lock.lock()\n        defer { state.lock.unlock() }\n\n        if let existing = state.inFlightTask {\n            return .join(existing)\n        }\n\n        state.nextAttemptID += 1\n        let attemptID = state.nextAttemptID\n        // Detached to avoid inheriting the caller's executor context (e.g. MainActor) and cancellation state.\n        #if DEBUG\n        let configuration = AttemptConfiguration(\n            environment: environment,\n            readStrategy: ClaudeOAuthKeychainReadStrategyPreference.current(),\n            keychainAccessDisabled: KeychainAccessGate.isDisabled,\n            cliAvailableOverride: self.cliAvailableOverrideForTesting,\n            touchAuthPathOverride: self.touchAuthPathOverrideForTesting,\n            keychainFingerprintOverride: self.keychainFingerprintOverrideForTesting)\n        let securityCLIReadOverride = ClaudeOAuthCredentialsStore.currentSecurityCLIReadOverrideForTesting()\n        #else\n        let configuration = AttemptConfiguration(\n            environment: environment,\n            readStrategy: ClaudeOAuthKeychainReadStrategyPreference.current(),\n            keychainAccessDisabled: KeychainAccessGate.isDisabled)\n        #endif\n        let task = Task.detached(priority: .utility) {\n            #if DEBUG\n            return await ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting(securityCLIReadOverride) {\n                await self.performAttempt(\n                    now: now,\n                    timeout: timeout,\n                    configuration: configuration,\n                    state: state)\n            }\n            #else\n            await self.performAttempt(\n                now: now,\n                timeout: timeout,\n                configuration: configuration,\n                state: state)\n            #endif\n        }\n        state.inFlightAttemptID = attemptID\n        state.inFlightTask = task\n        return .start(attemptID, task, state)\n    }\n\n    private static func performAttempt(\n        now: Date,\n        timeout: TimeInterval,\n        configuration: AttemptConfiguration,\n        state: AttemptStateStorage) async -> Outcome\n    {\n        guard self.isClaudeCLIAvailable(environment: configuration.environment, configuration: configuration) else {\n            self.log.info(\"Claude OAuth delegated refresh skipped: claude CLI unavailable\")\n            return .cliUnavailable\n        }\n\n        // Atomically reserve an attempt under the lock so concurrent callers don't race past isInCooldown() and start\n        // multiple touches/poll loops.\n        guard self.reserveAttemptIfNotInCooldown(now: now, state: state) else {\n            self.log.debug(\"Claude OAuth delegated refresh skipped by cooldown\")\n            return .skippedByCooldown\n        }\n\n        let baseline = self.currentKeychainChangeObservationBaseline(\n            readStrategy: configuration.readStrategy,\n            keychainAccessDisabled: configuration.keychainAccessDisabled,\n            configuration: configuration)\n        var touchError: Error?\n\n        do {\n            try await self.touchOAuthAuthPath(\n                timeout: timeout,\n                environment: configuration.environment,\n                configuration: configuration)\n        } catch {\n            touchError = error\n        }\n\n        // \"Touch succeeded\" must mean we actually observed the Claude keychain entry change.\n        // Otherwise we end up in a long cooldown with still-expired credentials.\n        let changed = await self.waitForClaudeKeychainChange(\n            from: baseline,\n            readStrategy: configuration.readStrategy,\n            keychainAccessDisabled: configuration.keychainAccessDisabled,\n            configuration: configuration,\n            timeout: min(max(timeout, 1), 2))\n        if changed {\n            self.recordAttempt(now: now, cooldown: self.defaultCooldownInterval, state: state)\n            self.log.info(\"Claude OAuth delegated refresh touch succeeded\")\n            return .attemptedSucceeded\n        }\n\n        self.recordAttempt(now: now, cooldown: self.shortCooldownInterval, state: state)\n        if let touchError {\n            let errorType = String(describing: type(of: touchError))\n            self.log.warning(\n                \"Claude OAuth delegated refresh touch failed\",\n                metadata: [\"errorType\": errorType])\n            self.log.debug(\"Claude OAuth delegated refresh touch error: \\(touchError.localizedDescription)\")\n            return .attemptedFailed(touchError.localizedDescription)\n        }\n\n        self.log.warning(\"Claude OAuth delegated refresh touch did not update Claude keychain\")\n        return .attemptedFailed(\"Claude keychain did not update after Claude CLI touch.\")\n    }\n\n    public static func isInCooldown(now: Date = Date()) -> Bool {\n        let state = self.currentStateStorage\n        state.lock.lock()\n        defer { state.lock.unlock() }\n        self.loadStateIfNeededLocked(state: state)\n        guard let lastAttemptAt = state.lastAttemptAt else { return false }\n        let cooldown = state.lastCooldownInterval ?? self.defaultCooldownInterval\n        return now.timeIntervalSince(lastAttemptAt) < cooldown\n    }\n\n    public static func cooldownRemainingSeconds(now: Date = Date()) -> Int? {\n        let state = self.currentStateStorage\n        state.lock.lock()\n        defer { state.lock.unlock() }\n        self.loadStateIfNeededLocked(state: state)\n        guard let lastAttemptAt = state.lastAttemptAt else { return nil }\n        let cooldown = state.lastCooldownInterval ?? self.defaultCooldownInterval\n        let remaining = cooldown - now.timeIntervalSince(lastAttemptAt)\n        guard remaining > 0 else { return nil }\n        return Int(remaining.rounded(.up))\n    }\n\n    public static func isClaudeCLIAvailable(\n        environment: [String: String] = ProcessInfo.processInfo.environment) -> Bool\n    {\n        self.isClaudeCLIAvailable(\n            environment: environment,\n            configuration: nil)\n    }\n\n    private static func isClaudeCLIAvailable(\n        environment: [String: String],\n        configuration: AttemptConfiguration?) -> Bool\n    {\n        #if DEBUG\n        if let override = configuration?.cliAvailableOverride ?? self.cliAvailableOverrideForTesting {\n            return override\n        }\n        #endif\n        return ClaudeCLIResolver.isAvailable(environment: environment)\n    }\n\n    private static func touchOAuthAuthPath(\n        timeout: TimeInterval,\n        environment: [String: String],\n        configuration: AttemptConfiguration?) async throws\n    {\n        #if DEBUG\n        if let override = configuration?.touchAuthPathOverride ?? self.touchAuthPathOverrideForTesting {\n            try await override(timeout, environment)\n            return\n        }\n        #endif\n        try await ClaudeStatusProbe.touchOAuthAuthPath(timeout: timeout, environment: environment)\n    }\n\n    private enum KeychainChangeObservationBaseline {\n        case securityFramework(fingerprint: ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint?)\n        case securityCLI(data: Data?)\n    }\n\n    private static func currentKeychainChangeObservationBaseline(\n        readStrategy: ClaudeOAuthKeychainReadStrategy,\n        keychainAccessDisabled: Bool,\n        configuration: AttemptConfiguration?) -> KeychainChangeObservationBaseline\n    {\n        if readStrategy == .securityCLIExperimental {\n            return .securityCLI(data: self.currentClaudeKeychainDataViaSecurityCLIForObservation(\n                readStrategy: readStrategy,\n                keychainAccessDisabled: keychainAccessDisabled,\n                interaction: .background))\n        }\n        return .securityFramework(fingerprint: self.currentClaudeKeychainFingerprint(configuration: configuration))\n    }\n\n    private static func waitForClaudeKeychainChange(\n        from baseline: KeychainChangeObservationBaseline,\n        readStrategy: ClaudeOAuthKeychainReadStrategy,\n        keychainAccessDisabled: Bool,\n        configuration: AttemptConfiguration?,\n        timeout: TimeInterval) async -> Bool\n    {\n        // Prefer correctness but bound the delay. Keychain writes can be slightly delayed after the CLI touch.\n        // Keep this short to avoid \"prompt storms\" on configurations where \"no UI\" queries can still surface UI.\n        let clampedTimeout = max(0, min(timeout, 2))\n        if clampedTimeout == 0 { return false }\n\n        let delays: [TimeInterval] = [0.2, 0.5, 0.8].filter { $0 <= clampedTimeout }\n        let deadline = Date().addingTimeInterval(clampedTimeout)\n\n        func isObservedChange() -> Bool {\n            switch baseline {\n            case let .securityFramework(fingerprintBefore):\n                // Treat \"no fingerprint\" as \"not observed\"; we only succeed if we can read a fingerprint and it\n                // differs.\n                guard let current = self.currentClaudeKeychainFingerprintForObservation(configuration: configuration)\n                else {\n                    return false\n                }\n                return current != fingerprintBefore\n            case let .securityCLI(dataBefore):\n                // In experimental mode, avoid Security.framework observation entirely and detect change from\n                // /usr/bin/security output only.\n                // If baseline capture failed (nil), treat observation as inconclusive and do not infer a change from\n                // a later successful read.\n                guard let dataBefore else { return false }\n                guard let current = self.currentClaudeKeychainDataViaSecurityCLIForObservation(\n                    readStrategy: readStrategy,\n                    keychainAccessDisabled: keychainAccessDisabled,\n                    interaction: .background)\n                else { return false }\n                return current != dataBefore\n            }\n        }\n\n        if isObservedChange() {\n            return true\n        }\n\n        for delay in delays {\n            if Date() >= deadline { break }\n            do {\n                try Task.checkCancellation()\n                try await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000))\n            } catch {\n                return false\n            }\n\n            if isObservedChange() {\n                return true\n            }\n        }\n\n        return false\n    }\n\n    private static func currentClaudeKeychainFingerprint(\n        configuration: AttemptConfiguration?) -> ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint?\n    {\n        #if DEBUG\n        if let override = configuration?.keychainFingerprintOverride ?? self.keychainFingerprintOverrideForTesting {\n            return override()\n        }\n        #endif\n        return ClaudeOAuthCredentialsStore.currentClaudeKeychainFingerprintWithoutPromptForAuthGate()\n    }\n\n    private static func currentClaudeKeychainFingerprintForObservation() -> ClaudeOAuthCredentialsStore\n        .ClaudeKeychainFingerprint?\n    {\n        self.currentClaudeKeychainFingerprintForObservation(configuration: nil)\n    }\n\n    private static func currentClaudeKeychainFingerprintForObservation(\n        configuration: AttemptConfiguration?) -> ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint?\n    {\n        #if DEBUG\n        if let override = configuration?.keychainFingerprintOverride ?? self.keychainFingerprintOverrideForTesting {\n            return override()\n        }\n        #endif\n\n        // Observation should not be blocked by the background cooldown gate; otherwise we can \"false fail\" even when\n        // the CLI refreshed successfully but we couldn't observe it due to a previous denied prompt/cooldown.\n        //\n        // This temporarily classifies the observation query as \"user initiated\" so it bypasses the gate that only\n        // applies to background probes. The query remains \"no UI\" and does not clear cooldown state itself.\n        return ProviderInteractionContext.$current.withValue(.userInitiated) {\n            ClaudeOAuthCredentialsStore.currentClaudeKeychainFingerprintWithoutPromptForAuthGate()\n        }\n    }\n\n    private static func currentClaudeKeychainDataViaSecurityCLIForObservation(\n        readStrategy: ClaudeOAuthKeychainReadStrategy,\n        keychainAccessDisabled: Bool,\n        interaction: ProviderInteraction) -> Data?\n    {\n        guard !keychainAccessDisabled else { return nil }\n        return ClaudeOAuthCredentialsStore.loadFromClaudeKeychainViaSecurityCLIIfEnabled(\n            interaction: interaction,\n            readStrategy: readStrategy)\n    }\n\n    private static func clearInFlightTaskIfStillCurrent(id: UInt64, state: AttemptStateStorage) {\n        state.lock.lock()\n        if state.inFlightAttemptID == id {\n            state.inFlightAttemptID = nil\n            state.inFlightTask = nil\n        }\n        state.lock.unlock()\n    }\n\n    private static func recordAttempt(now: Date, cooldown: TimeInterval, state: AttemptStateStorage) {\n        state.lock.lock()\n        defer { state.lock.unlock() }\n        self.loadStateIfNeededLocked(state: state)\n        state.lastAttemptAt = now\n        state.lastCooldownInterval = cooldown\n        guard state.persistsCooldown else { return }\n        UserDefaults.standard.set(now.timeIntervalSince1970, forKey: self.cooldownDefaultsKey)\n        UserDefaults.standard.set(cooldown, forKey: self.cooldownIntervalDefaultsKey)\n    }\n\n    private static func reserveAttemptIfNotInCooldown(now: Date, state: AttemptStateStorage) -> Bool {\n        state.lock.lock()\n        defer { state.lock.unlock() }\n        self.loadStateIfNeededLocked(state: state)\n\n        let cooldown = state.lastCooldownInterval ?? self.defaultCooldownInterval\n        if let lastAttemptAt = state.lastAttemptAt, now.timeIntervalSince(lastAttemptAt) < cooldown {\n            return false\n        }\n\n        // Reserve with a short cooldown; the final outcome will extend or keep it short.\n        state.lastAttemptAt = now\n        state.lastCooldownInterval = self.shortCooldownInterval\n        guard state.persistsCooldown else { return true }\n        UserDefaults.standard.set(now.timeIntervalSince1970, forKey: self.cooldownDefaultsKey)\n        UserDefaults.standard.set(self.shortCooldownInterval, forKey: self.cooldownIntervalDefaultsKey)\n        return true\n    }\n\n    private static func loadStateIfNeededLocked(state: AttemptStateStorage) {\n        guard !state.hasLoadedState else { return }\n        state.hasLoadedState = true\n        guard state.persistsCooldown else {\n            state.lastAttemptAt = nil\n            state.lastCooldownInterval = nil\n            return\n        }\n        guard let raw = UserDefaults.standard.object(forKey: self.cooldownDefaultsKey) as? Double else {\n            state.lastAttemptAt = nil\n            state.lastCooldownInterval = nil\n            return\n        }\n        state.lastAttemptAt = Date(timeIntervalSince1970: raw)\n        if let interval = UserDefaults.standard.object(forKey: self.cooldownIntervalDefaultsKey) as? Double {\n            state.lastCooldownInterval = interval\n        } else {\n            state.lastCooldownInterval = nil\n        }\n    }\n\n    #if DEBUG\n    @TaskLocal private static var stateStorageForTesting: AttemptStateStorage?\n    @TaskLocal static var cliAvailableOverrideForTesting: Bool?\n    @TaskLocal static var touchAuthPathOverrideForTesting: (@Sendable (\n        TimeInterval,\n        [String: String]) async throws -> Void)?\n    @TaskLocal static var keychainFingerprintOverrideForTesting: (@Sendable () -> ClaudeOAuthCredentialsStore\n        .ClaudeKeychainFingerprint?)?\n\n    static func withCLIAvailableOverrideForTesting<T>(\n        _ override: Bool?,\n        operation: () async throws -> T) async rethrows -> T\n    {\n        try await self.$cliAvailableOverrideForTesting.withValue(override) {\n            try await operation()\n        }\n    }\n\n    static func withTouchAuthPathOverrideForTesting<T>(\n        _ override: (@Sendable (TimeInterval, [String: String]) async throws -> Void)?,\n        operation: () async throws -> T) async rethrows -> T\n    {\n        try await self.$touchAuthPathOverrideForTesting.withValue(override) {\n            try await operation()\n        }\n    }\n\n    static func withKeychainFingerprintOverrideForTesting<T>(\n        _ override: (@Sendable () -> ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint?)?,\n        operation: () async throws -> T) async rethrows -> T\n    {\n        try await self.$keychainFingerprintOverrideForTesting.withValue(override) {\n            try await operation()\n        }\n    }\n\n    static func withIsolatedStateForTesting<T>(operation: () async throws -> T) async rethrows -> T {\n        let state = AttemptStateStorage(persistsCooldown: false)\n        return try await self.$stateStorageForTesting.withValue(state) {\n            try await operation()\n        }\n    }\n\n    static func resetForTesting() {\n        let state = self.currentStateStorage\n        state.lock.lock()\n        state.hasLoadedState = true\n        state.lastAttemptAt = nil\n        state.lastCooldownInterval = nil\n        state.inFlightAttemptID = nil\n        state.inFlightTask = nil\n        state.nextAttemptID = 0\n        state.lock.unlock()\n        guard state.persistsCooldown else { return }\n        UserDefaults.standard.removeObject(forKey: self.cooldownDefaultsKey)\n        UserDefaults.standard.removeObject(forKey: self.cooldownIntervalDefaultsKey)\n    }\n    #endif\n\n    private static var currentStateStorage: AttemptStateStorage {\n        #if DEBUG\n        self.stateStorageForTesting ?? self.sharedState\n        #else\n        self.sharedState\n        #endif\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthKeychainAccessGate.swift",
    "content": "import Foundation\n\n#if os(macOS)\nimport os.lock\n\npublic enum ClaudeOAuthKeychainAccessGate {\n    private struct State {\n        var loaded = false\n        var deniedUntil: Date?\n    }\n\n    private static let lock = OSAllocatedUnfairLock<State>(initialState: State())\n    private static let defaultsKey = \"claudeOAuthKeychainDeniedUntil\"\n    private static let cooldownInterval: TimeInterval = 60 * 60 * 6\n    @TaskLocal private static var taskOverrideShouldAllowPromptForTesting: Bool?\n    #if DEBUG\n    public final class DeniedUntilStore: @unchecked Sendable {\n        public var deniedUntil: Date?\n\n        public init() {}\n    }\n\n    @TaskLocal private static var taskDeniedUntilStoreOverrideForTesting: DeniedUntilStore?\n    #endif\n\n    public static func shouldAllowPrompt(now: Date = Date()) -> Bool {\n        guard !KeychainAccessGate.isDisabled else { return false }\n        if let override = self.taskOverrideShouldAllowPromptForTesting { return override }\n        #if DEBUG\n        if let store = self.taskDeniedUntilStoreOverrideForTesting {\n            if let deniedUntil = store.deniedUntil, deniedUntil > now {\n                return false\n            }\n            store.deniedUntil = nil\n            return true\n        }\n        #endif\n        return self.lock.withLock { state in\n            self.loadIfNeeded(&state)\n            if let deniedUntil = state.deniedUntil {\n                if deniedUntil > now {\n                    return false\n                }\n                state.deniedUntil = nil\n                self.persist(state)\n            }\n            return true\n        }\n    }\n\n    public static func recordDenied(now: Date = Date()) {\n        let deniedUntil = now.addingTimeInterval(self.cooldownInterval)\n        #if DEBUG\n        if let store = self.taskDeniedUntilStoreOverrideForTesting {\n            store.deniedUntil = deniedUntil\n            return\n        }\n        #endif\n        self.lock.withLock { state in\n            self.loadIfNeeded(&state)\n            state.deniedUntil = deniedUntil\n            self.persist(state)\n        }\n    }\n\n    /// Clears the cooldown so the next attempt can proceed. Intended for user-initiated repairs.\n    /// - Returns: true if a cooldown was present and cleared.\n    public static func clearDenied(now: Date = Date()) -> Bool {\n        #if DEBUG\n        if let store = self.taskDeniedUntilStoreOverrideForTesting {\n            guard let deniedUntil = store.deniedUntil, deniedUntil > now else {\n                store.deniedUntil = nil\n                return false\n            }\n            store.deniedUntil = nil\n            return true\n        }\n        #endif\n        return self.lock.withLock { state in\n            self.loadIfNeeded(&state)\n            guard let deniedUntil = state.deniedUntil, deniedUntil > now else {\n                state.deniedUntil = nil\n                self.persist(state)\n                return false\n            }\n            state.deniedUntil = nil\n            self.persist(state)\n            return true\n        }\n    }\n\n    #if DEBUG\n    static func withShouldAllowPromptOverrideForTesting<T>(\n        _ value: Bool?,\n        operation: () throws -> T) rethrows -> T\n    {\n        try self.$taskOverrideShouldAllowPromptForTesting.withValue(value) {\n            try operation()\n        }\n    }\n\n    static func withShouldAllowPromptOverrideForTesting<T>(\n        _ value: Bool?,\n        operation: () async throws -> T) async rethrows -> T\n    {\n        try await self.$taskOverrideShouldAllowPromptForTesting.withValue(value) {\n            try await operation()\n        }\n    }\n\n    public static func withDeniedUntilStoreOverrideForTesting<T>(\n        _ store: DeniedUntilStore?,\n        operation: () throws -> T) rethrows -> T\n    {\n        try self.$taskDeniedUntilStoreOverrideForTesting.withValue(store) {\n            try operation()\n        }\n    }\n\n    public static func withDeniedUntilStoreOverrideForTesting<T>(\n        _ store: DeniedUntilStore?,\n        operation: () async throws -> T) async rethrows -> T\n    {\n        try await self.$taskDeniedUntilStoreOverrideForTesting.withValue(store) {\n            try await operation()\n        }\n    }\n\n    public static var currentDeniedUntilStoreOverrideForTesting: DeniedUntilStore? {\n        self.taskDeniedUntilStoreOverrideForTesting\n    }\n\n    public static func resetForTesting() {\n        self.lock.withLock { state in\n            // Keep deterministic during tests: avoid re-loading UserDefaults written by unrelated code paths.\n            state.loaded = true\n            state.deniedUntil = nil\n            UserDefaults.standard.removeObject(forKey: self.defaultsKey)\n        }\n    }\n\n    public static func resetInMemoryForTesting() {\n        self.lock.withLock { state in\n            state.loaded = false\n            state.deniedUntil = nil\n        }\n    }\n    #endif\n\n    private static func loadIfNeeded(_ state: inout State) {\n        guard !state.loaded else { return }\n        state.loaded = true\n        if let raw = UserDefaults.standard.object(forKey: self.defaultsKey) as? Double {\n            state.deniedUntil = Date(timeIntervalSince1970: raw)\n        }\n    }\n\n    private static func persist(_ state: State) {\n        if let deniedUntil = state.deniedUntil {\n            UserDefaults.standard.set(deniedUntil.timeIntervalSince1970, forKey: self.defaultsKey)\n        } else {\n            UserDefaults.standard.removeObject(forKey: self.defaultsKey)\n        }\n    }\n}\n#else\npublic enum ClaudeOAuthKeychainAccessGate {\n    public static func shouldAllowPrompt(now _: Date = Date()) -> Bool {\n        true\n    }\n\n    public static func recordDenied(now _: Date = Date()) {}\n\n    public static func clearDenied(now _: Date = Date()) -> Bool {\n        false\n    }\n\n    #if DEBUG\n    public static func resetForTesting() {}\n\n    public static func resetInMemoryForTesting() {}\n    #endif\n}\n#endif\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthKeychainPromptMode.swift",
    "content": "import Foundation\n\npublic enum ClaudeOAuthKeychainPromptMode: String, Sendable, Codable, CaseIterable {\n    case never\n    case onlyOnUserAction\n    case always\n}\n\npublic enum ClaudeOAuthKeychainPromptPreference {\n    private static let userDefaultsKey = \"claudeOAuthKeychainPromptMode\"\n\n    #if DEBUG\n    @TaskLocal private static var taskOverride: ClaudeOAuthKeychainPromptMode?\n    #endif\n\n    public static func current(userDefaults: UserDefaults = .standard) -> ClaudeOAuthKeychainPromptMode {\n        self.effectiveMode(userDefaults: userDefaults)\n    }\n\n    public static func storedMode(userDefaults: UserDefaults = .standard) -> ClaudeOAuthKeychainPromptMode {\n        #if DEBUG\n        if let taskOverride { return taskOverride }\n        #endif\n        if let raw = userDefaults.string(forKey: self.userDefaultsKey),\n           let mode = ClaudeOAuthKeychainPromptMode(rawValue: raw)\n        {\n            return mode\n        }\n        return .onlyOnUserAction\n    }\n\n    public static func isApplicable(\n        readStrategy: ClaudeOAuthKeychainReadStrategy = ClaudeOAuthKeychainReadStrategyPreference.current()) -> Bool\n    {\n        readStrategy == .securityFramework\n    }\n\n    public static func effectiveMode(\n        userDefaults: UserDefaults = .standard,\n        readStrategy: ClaudeOAuthKeychainReadStrategy = ClaudeOAuthKeychainReadStrategyPreference.current())\n        -> ClaudeOAuthKeychainPromptMode\n    {\n        guard self.isApplicable(readStrategy: readStrategy) else {\n            return .always\n        }\n        return self.storedMode(userDefaults: userDefaults)\n    }\n\n    public static func securityFrameworkFallbackMode(\n        userDefaults: UserDefaults = .standard,\n        readStrategy: ClaudeOAuthKeychainReadStrategy = ClaudeOAuthKeychainReadStrategyPreference.current())\n        -> ClaudeOAuthKeychainPromptMode\n    {\n        if readStrategy == .securityCLIExperimental {\n            return self.storedMode(userDefaults: userDefaults)\n        }\n        return self.effectiveMode(userDefaults: userDefaults, readStrategy: readStrategy)\n    }\n\n    #if DEBUG\n    public static func withTaskOverrideForTesting<T>(\n        _ mode: ClaudeOAuthKeychainPromptMode?,\n        operation: () throws -> T) rethrows -> T\n    {\n        try self.$taskOverride.withValue(mode) {\n            try operation()\n        }\n    }\n\n    public static func withTaskOverrideForTesting<T>(\n        _ mode: ClaudeOAuthKeychainPromptMode?,\n        operation: () async throws -> T) async rethrows -> T\n    {\n        try await self.$taskOverride.withValue(mode) {\n            try await operation()\n        }\n    }\n\n    public static var currentTaskOverrideForTesting: ClaudeOAuthKeychainPromptMode? {\n        self.taskOverride\n    }\n    #endif\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthKeychainQueryTiming.swift",
    "content": "import Dispatch\nimport Foundation\n\n#if os(macOS)\nimport Security\n\nenum ClaudeOAuthKeychainQueryTiming {\n    static func copyMatching(_ query: [String: Any]) -> (status: OSStatus, result: AnyObject?, durationMs: Double) {\n        var result: AnyObject?\n        let startedAtNs = DispatchTime.now().uptimeNanoseconds\n        let status = SecItemCopyMatching(query as CFDictionary, &result)\n        let durationMs = Double(DispatchTime.now().uptimeNanoseconds - startedAtNs) / 1_000_000.0\n        return (status, result, durationMs)\n    }\n\n    static func backoffIfSlowNoUIQuery(_ durationMs: Double, _ service: String, _ log: CodexBarLogger) -> Bool {\n        // Intentionally no longer treats \"slow\" no-UI Keychain queries as a denial. Some systems can have\n        // non-deterministic timing characteristics that would make this backoff too aggressive and surprising.\n        //\n        // Keep this hook so call sites can cheaply log slow queries during debugging without changing behavior.\n        guard ProviderInteractionContext.current == .background, durationMs > 1000 else { return false }\n        log.debug(\n            \"Claude keychain no-UI query was slow\",\n            metadata: [\n                \"service\": service,\n                \"duration_ms\": String(format: \"%.2f\", durationMs),\n            ])\n        return false\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthKeychainReadStrategy.swift",
    "content": "import Foundation\n\npublic enum ClaudeOAuthKeychainReadStrategy: String, Sendable, Codable, CaseIterable {\n    case securityFramework\n    case securityCLIExperimental\n}\n\npublic enum ClaudeOAuthKeychainReadStrategyPreference {\n    private static let userDefaultsKey = \"claudeOAuthKeychainReadStrategy\"\n\n    #if DEBUG\n    @TaskLocal private static var taskOverride: ClaudeOAuthKeychainReadStrategy?\n    #endif\n\n    public static func current(userDefaults: UserDefaults = .standard) -> ClaudeOAuthKeychainReadStrategy {\n        #if DEBUG\n        if let taskOverride { return taskOverride }\n        #endif\n        if let raw = userDefaults.string(forKey: self.userDefaultsKey),\n           let strategy = ClaudeOAuthKeychainReadStrategy(rawValue: raw)\n        {\n            return strategy\n        }\n        return .securityFramework\n    }\n\n    #if DEBUG\n    public static func withTaskOverrideForTesting<T>(\n        _ strategy: ClaudeOAuthKeychainReadStrategy?,\n        operation: () throws -> T) rethrows -> T\n    {\n        try self.$taskOverride.withValue(strategy) {\n            try operation()\n        }\n    }\n\n    public static func withTaskOverrideForTesting<T>(\n        _ strategy: ClaudeOAuthKeychainReadStrategy?,\n        operation: () async throws -> T) async rethrows -> T\n    {\n        try await self.$taskOverride.withValue(strategy) {\n            try await operation()\n        }\n    }\n\n    public static var currentTaskOverrideForTesting: ClaudeOAuthKeychainReadStrategy? {\n        self.taskOverride\n    }\n    #endif\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthMutableKeychainOverrides.swift",
    "content": "import Foundation\n\n#if DEBUG\nextension ClaudeOAuthCredentialsStore {\n    final class ClaudeKeychainOverrideStore: @unchecked Sendable {\n        var data: Data?\n        var fingerprint: ClaudeKeychainFingerprint?\n\n        init(data: Data? = nil, fingerprint: ClaudeKeychainFingerprint? = nil) {\n            self.data = data\n            self.fingerprint = fingerprint\n        }\n    }\n\n    @TaskLocal static var taskClaudeKeychainOverrideStore: ClaudeKeychainOverrideStore?\n\n    static func withMutableClaudeKeychainOverrideStoreForTesting<T>(\n        _ store: ClaudeKeychainOverrideStore?,\n        operation: () throws -> T) rethrows -> T\n    {\n        try self.$taskClaudeKeychainOverrideStore.withValue(store) {\n            try operation()\n        }\n    }\n\n    static func withMutableClaudeKeychainOverrideStoreForTesting<T>(\n        _ store: ClaudeKeychainOverrideStore?,\n        operation: () async throws -> T) async rethrows -> T\n    {\n        try await self.$taskClaudeKeychainOverrideStore.withValue(store) {\n            try await operation()\n        }\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthRefreshFailureGate.swift",
    "content": "import Foundation\n\n#if os(macOS)\nimport os.lock\n\npublic enum ClaudeOAuthRefreshFailureGate {\n    public enum BlockStatus: Equatable, Sendable {\n        case terminal(reason: String?, failures: Int)\n        case transient(until: Date, failures: Int)\n    }\n\n    struct AuthFingerprint: Codable, Equatable {\n        let keychain: ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint?\n        let credentialsFile: String?\n    }\n\n    private struct State {\n        var loaded = false\n        var terminalFailureCount = 0\n        var transientFailureCount = 0\n        var isTerminalBlocked = false\n        var transientBlockedUntil: Date?\n        var fingerprintAtFailure: AuthFingerprint?\n        var lastCredentialsRecheckAt: Date?\n        var terminalReason: String?\n    }\n\n    private static let lock = OSAllocatedUnfairLock<State>(initialState: State())\n    private static let blockedUntilKey = \"claudeOAuthRefreshBackoffBlockedUntilV1\" // legacy (migration)\n    private static let failureCountKey = \"claudeOAuthRefreshBackoffFailureCountV1\" // legacy + terminal count\n    private static let fingerprintKey = \"claudeOAuthRefreshBackoffFingerprintV2\"\n    private static let terminalBlockedKey = \"claudeOAuthRefreshTerminalBlockedV1\"\n    private static let terminalReasonKey = \"claudeOAuthRefreshTerminalReasonV1\"\n    private static let transientBlockedUntilKey = \"claudeOAuthRefreshTransientBlockedUntilV1\"\n    private static let transientFailureCountKey = \"claudeOAuthRefreshTransientFailureCountV1\"\n\n    private static let log = CodexBarLog.logger(LogCategories.claudeUsage)\n    private static let minimumCredentialsRecheckInterval: TimeInterval = 15\n    private static let unknownFingerprint = AuthFingerprint(keychain: nil, credentialsFile: nil)\n    private static let transientBaseInterval: TimeInterval = 60 * 5\n    private static let transientMaxInterval: TimeInterval = 60 * 60 * 6\n\n    #if DEBUG\n    @TaskLocal static var shouldAttemptOverride: Bool?\n    private nonisolated(unsafe) static var fingerprintProviderOverride: (() -> AuthFingerprint?)?\n\n    static func setFingerprintProviderOverrideForTesting(_ provider: (() -> AuthFingerprint?)?) {\n        self.fingerprintProviderOverride = provider\n    }\n\n    public static func resetInMemoryStateForTesting() {\n        self.lock.withLock { state in\n            state.loaded = false\n            state.terminalFailureCount = 0\n            state.transientFailureCount = 0\n            state.isTerminalBlocked = false\n            state.transientBlockedUntil = nil\n            state.fingerprintAtFailure = nil\n            state.lastCredentialsRecheckAt = nil\n            state.terminalReason = nil\n        }\n    }\n\n    public static func resetForTesting() {\n        self.lock.withLock { state in\n            state.loaded = false\n            state.terminalFailureCount = 0\n            state.transientFailureCount = 0\n            state.isTerminalBlocked = false\n            state.transientBlockedUntil = nil\n            state.fingerprintAtFailure = nil\n            state.lastCredentialsRecheckAt = nil\n            state.terminalReason = nil\n            UserDefaults.standard.removeObject(forKey: self.blockedUntilKey)\n            UserDefaults.standard.removeObject(forKey: self.failureCountKey)\n            UserDefaults.standard.removeObject(forKey: self.fingerprintKey)\n            UserDefaults.standard.removeObject(forKey: self.terminalBlockedKey)\n            UserDefaults.standard.removeObject(forKey: self.terminalReasonKey)\n            UserDefaults.standard.removeObject(forKey: self.transientBlockedUntilKey)\n            UserDefaults.standard.removeObject(forKey: self.transientFailureCountKey)\n        }\n    }\n    #endif\n\n    public static func shouldAttempt(now: Date = Date()) -> Bool {\n        #if DEBUG\n        if let override = self.shouldAttemptOverride { return override }\n        #endif\n\n        return self.lock.withLock { state in\n            let didMigrate = self.loadIfNeeded(&state, now: now)\n            if didMigrate {\n                self.persist(state)\n            }\n\n            if state.isTerminalBlocked {\n                guard self.shouldRecheckCredentials(now: now, state: state) else { return false }\n\n                state.lastCredentialsRecheckAt = now\n                if self.hasCredentialsChangedSinceFailure(state) {\n                    self.resetState(&state)\n                    self.persist(state)\n                    return true\n                }\n\n                self.log.debug(\n                    \"Claude OAuth refresh blocked until auth changes\",\n                    metadata: [\n                        \"terminalFailures\": \"\\(state.terminalFailureCount)\",\n                        \"reason\": state.terminalReason ?? \"nil\",\n                    ])\n                return false\n            }\n\n            if let blockedUntil = state.transientBlockedUntil {\n                if blockedUntil <= now {\n                    self.clearTransientState(&state)\n                    // Once transient backoff expires, forget its auth baseline so future failures capture fresh\n                    // fingerprints and so we don't ratchet backoff across unrelated intermittent failures.\n                    state.fingerprintAtFailure = nil\n                    state.lastCredentialsRecheckAt = nil\n                    self.persist(state)\n                    return true\n                }\n\n                if self.shouldRecheckCredentials(now: now, state: state) {\n                    state.lastCredentialsRecheckAt = now\n                    if self.hasCredentialsChangedSinceFailure(state) {\n                        self.resetState(&state)\n                        self.persist(state)\n                        return true\n                    }\n                }\n\n                self.log.debug(\n                    \"Claude OAuth refresh transient backoff active\",\n                    metadata: [\n                        \"until\": \"\\(blockedUntil.timeIntervalSince1970)\",\n                        \"transientFailures\": \"\\(state.transientFailureCount)\",\n                    ])\n                return false\n            }\n\n            return true\n        }\n    }\n\n    public static func currentBlockStatus(now: Date = Date()) -> BlockStatus? {\n        self.lock.withLock { state in\n            _ = self.loadIfNeeded(&state, now: now)\n            if state.isTerminalBlocked {\n                return .terminal(reason: state.terminalReason, failures: state.terminalFailureCount)\n            }\n            if let blockedUntil = state.transientBlockedUntil, blockedUntil > now {\n                return .transient(until: blockedUntil, failures: state.transientFailureCount)\n            }\n            return nil\n        }\n    }\n\n    public static func recordTerminalAuthFailure(now: Date = Date()) {\n        self.lock.withLock { state in\n            _ = self.loadIfNeeded(&state, now: now)\n            state.terminalFailureCount += 1\n            state.isTerminalBlocked = true\n            state.terminalReason = \"invalid_grant\"\n            state.fingerprintAtFailure = self.currentFingerprint() ?? self.unknownFingerprint\n            state.lastCredentialsRecheckAt = now\n            self.clearTransientState(&state)\n            self.persist(state)\n        }\n    }\n\n    public static func recordTransientFailure(now: Date = Date()) {\n        self.lock.withLock { state in\n            _ = self.loadIfNeeded(&state, now: now)\n\n            // Keep terminal blocking monotonic: once we know auth is rejected (e.g. invalid_grant),\n            // do not downgrade it to time-based backoff unless auth changes (fingerprint) or we record success.\n            guard !state.isTerminalBlocked else { return }\n\n            self.clearTerminalState(&state)\n\n            state.transientFailureCount += 1\n            let interval = self.transientCooldownInterval(failures: state.transientFailureCount)\n            state.transientBlockedUntil = now.addingTimeInterval(interval)\n            state.fingerprintAtFailure = self.currentFingerprint() ?? self.unknownFingerprint\n            state.lastCredentialsRecheckAt = now\n            self.persist(state)\n        }\n    }\n\n    public static func recordAuthFailure(now: Date = Date()) {\n        // Legacy shim: treat as terminal auth failure.\n        self.recordTerminalAuthFailure(now: now)\n    }\n\n    public static func recordSuccess() {\n        self.lock.withLock { state in\n            _ = self.loadIfNeeded(&state, now: Date())\n            self.resetState(&state)\n            self.persist(state)\n        }\n    }\n\n    private static func shouldRecheckCredentials(now: Date, state: State) -> Bool {\n        guard let last = state.lastCredentialsRecheckAt else { return true }\n        return now.timeIntervalSince(last) >= self.minimumCredentialsRecheckInterval\n    }\n\n    private static func hasCredentialsChangedSinceFailure(_ state: State) -> Bool {\n        guard let current = self.currentFingerprint() else { return false }\n        guard let prior = state.fingerprintAtFailure else { return false }\n        return current != prior\n    }\n\n    private static func currentFingerprint() -> AuthFingerprint? {\n        #if DEBUG\n        if let override = self.fingerprintProviderOverride { return override() }\n        #endif\n        return AuthFingerprint(\n            keychain: ClaudeOAuthCredentialsStore.currentClaudeKeychainFingerprintWithoutPromptForAuthGate(),\n            credentialsFile: ClaudeOAuthCredentialsStore.currentCredentialsFileFingerprintWithoutPromptForAuthGate())\n    }\n\n    private static func loadIfNeeded(_ state: inout State, now: Date) -> Bool {\n        state.loaded = true\n        var didMutate = false\n\n        // Always refresh persisted fields from UserDefaults, even after first load.\n        //\n        // This avoids stale state when UserDefaults are modified while the app is running (or during tests),\n        // while still keeping ephemeral throttling state (like lastCredentialsRecheckAt) in memory.\n        state.terminalFailureCount = UserDefaults.standard.integer(forKey: self.failureCountKey)\n        state.transientFailureCount = UserDefaults.standard.integer(forKey: self.transientFailureCountKey)\n\n        if let raw = UserDefaults.standard.object(forKey: self.transientBlockedUntilKey) as? Double {\n            state.transientBlockedUntil = Date(timeIntervalSince1970: raw)\n        }\n\n        let legacyBlockedUntil = (UserDefaults.standard.object(forKey: self.blockedUntilKey) as? Double)\n            .map { Date(timeIntervalSince1970: $0) }\n        let legacyFailureCount = UserDefaults.standard.integer(forKey: self.failureCountKey)\n\n        if let data = UserDefaults.standard.data(forKey: self.fingerprintKey) {\n            state.fingerprintAtFailure = (try? JSONDecoder().decode(AuthFingerprint.self, from: data))\n        } else {\n            state.fingerprintAtFailure = nil\n        }\n\n        if UserDefaults.standard.object(forKey: self.terminalBlockedKey) != nil {\n            state.isTerminalBlocked = UserDefaults.standard.bool(forKey: self.terminalBlockedKey)\n            state.terminalReason = UserDefaults.standard.string(forKey: self.terminalReasonKey)\n            if legacyBlockedUntil != nil {\n                didMutate = true\n            }\n        } else {\n            // Migration: legacy keys represented a time-based backoff. Migrate to transient backoff (never terminal)\n            // unless we already have new transient keys persisted.\n            if UserDefaults.standard.object(forKey: self.transientFailureCountKey) == nil,\n               UserDefaults.standard.object(forKey: self.transientBlockedUntilKey) == nil,\n               legacyBlockedUntil != nil || legacyFailureCount > 0\n            {\n                state.isTerminalBlocked = false\n                state.terminalReason = nil\n                state.terminalFailureCount = 0\n\n                if let legacyBlockedUntil, legacyBlockedUntil > now {\n                    state.transientFailureCount = max(legacyFailureCount, 0)\n                    state.transientBlockedUntil = legacyBlockedUntil\n                } else {\n                    state.transientFailureCount = 0\n                    state.transientBlockedUntil = nil\n                }\n                didMutate = true\n            }\n        }\n\n        if state.isTerminalBlocked || state.transientBlockedUntil != nil, state.fingerprintAtFailure == nil {\n            state.fingerprintAtFailure = self.unknownFingerprint\n            didMutate = true\n        }\n\n        if legacyBlockedUntil != nil {\n            didMutate = true\n        }\n\n        return didMutate\n    }\n\n    private static func persist(_ state: State) {\n        UserDefaults.standard.set(state.terminalFailureCount, forKey: self.failureCountKey)\n        UserDefaults.standard.set(state.isTerminalBlocked, forKey: self.terminalBlockedKey)\n        if let reason = state.terminalReason {\n            UserDefaults.standard.set(reason, forKey: self.terminalReasonKey)\n        } else {\n            UserDefaults.standard.removeObject(forKey: self.terminalReasonKey)\n        }\n\n        UserDefaults.standard.set(state.transientFailureCount, forKey: self.transientFailureCountKey)\n        if let blockedUntil = state.transientBlockedUntil {\n            UserDefaults.standard.set(blockedUntil.timeIntervalSince1970, forKey: self.transientBlockedUntilKey)\n        } else {\n            UserDefaults.standard.removeObject(forKey: self.transientBlockedUntilKey)\n        }\n\n        UserDefaults.standard.removeObject(forKey: self.blockedUntilKey)\n\n        if let fingerprint = state.fingerprintAtFailure,\n           let data = try? JSONEncoder().encode(fingerprint)\n        {\n            UserDefaults.standard.set(data, forKey: self.fingerprintKey)\n        } else {\n            UserDefaults.standard.removeObject(forKey: self.fingerprintKey)\n        }\n    }\n\n    private static func transientCooldownInterval(failures: Int) -> TimeInterval {\n        guard failures > 0 else { return 0 }\n        let factor = pow(2.0, Double(failures - 1))\n        return min(self.transientBaseInterval * factor, self.transientMaxInterval)\n    }\n\n    private static func clearTerminalState(_ state: inout State) {\n        state.terminalFailureCount = 0\n        state.isTerminalBlocked = false\n        state.terminalReason = nil\n    }\n\n    private static func clearTransientState(_ state: inout State) {\n        state.transientFailureCount = 0\n        state.transientBlockedUntil = nil\n    }\n\n    private static func resetState(_ state: inout State) {\n        self.clearTerminalState(&state)\n        self.clearTransientState(&state)\n        state.fingerprintAtFailure = nil\n        state.lastCredentialsRecheckAt = nil\n    }\n}\n#else\npublic enum ClaudeOAuthRefreshFailureGate {\n    public enum BlockStatus: Equatable, Sendable {\n        case terminal(reason: String?, failures: Int)\n        case transient(until: Date, failures: Int)\n    }\n\n    public static func shouldAttempt(now _: Date = Date()) -> Bool {\n        true\n    }\n\n    public static func currentBlockStatus(now _: Date = Date()) -> BlockStatus? {\n        nil\n    }\n\n    public static func recordTerminalAuthFailure(now _: Date = Date()) {}\n\n    public static func recordTransientFailure(now _: Date = Date()) {}\n\n    public static func recordAuthFailure(now _: Date = Date()) {}\n\n    public static func recordSuccess() {}\n\n    #if DEBUG\n    static func setFingerprintProviderOverrideForTesting(_: (() -> Any?)?) {}\n    public static func resetInMemoryStateForTesting() {}\n    public static func resetForTesting() {}\n    #endif\n}\n#endif\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthUsageFetcher.swift",
    "content": "import Foundation\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n\npublic enum ClaudeOAuthFetchError: LocalizedError, Sendable {\n    case unauthorized\n    case invalidResponse\n    case serverError(Int, String?)\n    case networkError(Error)\n\n    public var errorDescription: String? {\n        switch self {\n        case .unauthorized:\n            return \"Claude OAuth request unauthorized. Run `claude` to re-authenticate.\"\n        case .invalidResponse:\n            return \"Claude OAuth response was invalid.\"\n        case let .serverError(code, body):\n            if let body, !body.isEmpty {\n                let cleaned = body\n                    .replacingOccurrences(of: \"\\n\", with: \" \")\n                    .trimmingCharacters(in: .whitespacesAndNewlines)\n                let shortened = cleaned.count > 400 ? String(cleaned.prefix(400)) + \"…\" : cleaned\n                return \"Claude OAuth error: HTTP \\(code) – \\(shortened)\"\n            }\n            return \"Claude OAuth error: HTTP \\(code)\"\n        case let .networkError(error):\n            return \"Claude OAuth network error: \\(error.localizedDescription)\"\n        }\n    }\n}\n\nenum ClaudeOAuthUsageFetcher {\n    private static let baseURL = \"https://api.anthropic.com\"\n    private static let usagePath = \"/api/oauth/usage\"\n    private static let betaHeader = \"oauth-2025-04-20\"\n    private static let fallbackClaudeCodeVersion = \"2.1.0\"\n\n    static func fetchUsage(accessToken: String) async throws -> OAuthUsageResponse {\n        guard let url = URL(string: baseURL + usagePath) else {\n            throw ClaudeOAuthFetchError.invalidResponse\n        }\n\n        var request = URLRequest(url: url)\n        request.httpMethod = \"GET\"\n        request.timeoutInterval = 30\n        request.setValue(\"Bearer \\(accessToken)\", forHTTPHeaderField: \"Authorization\")\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Accept\")\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Content-Type\")\n        // OAuth usage endpoint currently requires the beta header.\n        request.setValue(Self.betaHeader, forHTTPHeaderField: \"anthropic-beta\")\n        request.setValue(Self.claudeCodeUserAgent(), forHTTPHeaderField: \"User-Agent\")\n\n        do {\n            let (data, response) = try await URLSession.shared.data(for: request)\n            guard let http = response as? HTTPURLResponse else {\n                throw ClaudeOAuthFetchError.invalidResponse\n            }\n            switch http.statusCode {\n            case 200:\n                return try Self.decodeUsageResponse(data)\n            case 401:\n                throw ClaudeOAuthFetchError.unauthorized\n            case 403:\n                let body = String(data: data, encoding: .utf8)\n                throw ClaudeOAuthFetchError.serverError(http.statusCode, body)\n            default:\n                let body = String(data: data, encoding: .utf8)\n                throw ClaudeOAuthFetchError.serverError(http.statusCode, body)\n            }\n        } catch let error as ClaudeOAuthFetchError {\n            throw error\n        } catch {\n            throw ClaudeOAuthFetchError.networkError(error)\n        }\n    }\n\n    static func decodeUsageResponse(_ data: Data) throws -> OAuthUsageResponse {\n        let decoder = JSONDecoder()\n        return try decoder.decode(OAuthUsageResponse.self, from: data)\n    }\n\n    static func parseISO8601Date(_ string: String?) -> Date? {\n        guard let string, !string.isEmpty else { return nil }\n        let formatter = ISO8601DateFormatter()\n        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]\n        if let date = formatter.date(from: string) { return date }\n        formatter.formatOptions = [.withInternetDateTime]\n        return formatter.date(from: string)\n    }\n\n    private static func claudeCodeUserAgent() -> String {\n        self.claudeCodeUserAgent(versionString: ProviderVersionDetector.claudeVersion())\n    }\n\n    private static func claudeCodeUserAgent(versionString: String?) -> String {\n        let version = self.normalizedClaudeCodeVersion(versionString) ?? self.fallbackClaudeCodeVersion\n        return \"claude-code/\\(version)\"\n    }\n\n    private static func normalizedClaudeCodeVersion(_ versionString: String?) -> String? {\n        guard let raw = versionString?.trimmingCharacters(in: .whitespacesAndNewlines), !raw.isEmpty else {\n            return nil\n        }\n        let token = raw.split(whereSeparator: \\.isWhitespace).first.map(String.init) ?? raw\n        let trimmed = token.trimmingCharacters(in: .whitespacesAndNewlines)\n        return trimmed.isEmpty ? nil : trimmed\n    }\n}\n\nstruct OAuthUsageResponse: Decodable {\n    let fiveHour: OAuthUsageWindow?\n    let sevenDay: OAuthUsageWindow?\n    let sevenDayOAuthApps: OAuthUsageWindow?\n    let sevenDayOpus: OAuthUsageWindow?\n    let sevenDaySonnet: OAuthUsageWindow?\n    let iguanaNecktie: OAuthUsageWindow?\n    let extraUsage: OAuthExtraUsage?\n\n    enum CodingKeys: String, CodingKey {\n        case fiveHour = \"five_hour\"\n        case sevenDay = \"seven_day\"\n        case sevenDayOAuthApps = \"seven_day_oauth_apps\"\n        case sevenDayOpus = \"seven_day_opus\"\n        case sevenDaySonnet = \"seven_day_sonnet\"\n        case iguanaNecktie = \"iguana_necktie\"\n        case extraUsage = \"extra_usage\"\n    }\n}\n\nstruct OAuthUsageWindow: Decodable {\n    let utilization: Double?\n    let resetsAt: String?\n\n    enum CodingKeys: String, CodingKey {\n        case utilization\n        case resetsAt = \"resets_at\"\n    }\n}\n\nstruct OAuthExtraUsage: Decodable {\n    let isEnabled: Bool?\n    let monthlyLimit: Double?\n    let usedCredits: Double?\n    let utilization: Double?\n    let currency: String?\n\n    enum CodingKeys: String, CodingKey {\n        case isEnabled = \"is_enabled\"\n        case monthlyLimit = \"monthly_limit\"\n        case usedCredits = \"used_credits\"\n        case utilization\n        case currency\n    }\n}\n\n#if DEBUG\nextension ClaudeOAuthUsageFetcher {\n    static func _decodeUsageResponseForTesting(_ data: Data) throws -> OAuthUsageResponse {\n        try self.decodeUsageResponse(data)\n    }\n\n    static func _userAgentForTesting(versionString: String?) -> String {\n        self.claudeCodeUserAgent(versionString: versionString)\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Claude/ClaudePlan.swift",
    "content": "import Foundation\n\npublic enum ClaudePlan: String, CaseIterable, Sendable {\n    case max\n    case pro\n    case team\n    case enterprise\n    case ultra\n\n    public var brandedLoginMethod: String {\n        switch self {\n        case .max:\n            \"Claude Max\"\n        case .pro:\n            \"Claude Pro\"\n        case .team:\n            \"Claude Team\"\n        case .enterprise:\n            \"Claude Enterprise\"\n        case .ultra:\n            \"Claude Ultra\"\n        }\n    }\n\n    public var compactLoginMethod: String {\n        switch self {\n        case .max:\n            \"Max\"\n        case .pro:\n            \"Pro\"\n        case .team:\n            \"Team\"\n        case .enterprise:\n            \"Enterprise\"\n        case .ultra:\n            \"Ultra\"\n        }\n    }\n\n    public var countsAsSubscription: Bool {\n        switch self {\n        case .max, .pro, .team, .ultra:\n            true\n        case .enterprise:\n            false\n        }\n    }\n\n    public static func fromOAuthRateLimitTier(_ rateLimitTier: String?) -> Self? {\n        self.fromRateLimitTier(rateLimitTier)\n    }\n\n    public static func fromWebAccount(rateLimitTier: String?, billingType: String?) -> Self? {\n        if let plan = self.fromRateLimitTier(rateLimitTier) {\n            return plan\n        }\n\n        let tier = Self.normalized(rateLimitTier)\n        let billing = Self.normalized(billingType)\n        if billing.contains(\"stripe\"), tier.contains(\"claude\") {\n            return .pro\n        }\n        return nil\n    }\n\n    public static func fromCompatibilityLoginMethod(_ loginMethod: String?) -> Self? {\n        let words = Self.normalizedWords(loginMethod)\n        if words.isEmpty {\n            return nil\n        }\n        if words.contains(\"max\") {\n            return .max\n        }\n        if words.contains(\"pro\") {\n            return .pro\n        }\n        if words.contains(\"team\") {\n            return .team\n        }\n        if words.contains(\"enterprise\") {\n            return .enterprise\n        }\n        if words.contains(\"ultra\") {\n            return .ultra\n        }\n        return nil\n    }\n\n    public static func oauthLoginMethod(rateLimitTier: String?) -> String? {\n        self.fromOAuthRateLimitTier(rateLimitTier)?.brandedLoginMethod\n    }\n\n    public static func webLoginMethod(rateLimitTier: String?, billingType: String?) -> String? {\n        self.fromWebAccount(rateLimitTier: rateLimitTier, billingType: billingType)?.brandedLoginMethod\n    }\n\n    public static func cliCompatibilityLoginMethod(_ loginMethod: String?) -> String? {\n        guard let loginMethod = loginMethod?.trimmingCharacters(in: .whitespacesAndNewlines),\n              !loginMethod.isEmpty\n        else {\n            return nil\n        }\n\n        if let plan = self.fromCompatibilityLoginMethod(loginMethod) {\n            return plan.compactLoginMethod\n        }\n\n        return loginMethod\n    }\n\n    public static func isSubscriptionLoginMethod(_ loginMethod: String?) -> Bool {\n        self.fromCompatibilityLoginMethod(loginMethod)?.countsAsSubscription ?? false\n    }\n\n    private static func fromRateLimitTier(_ rateLimitTier: String?) -> Self? {\n        let tier = Self.normalized(rateLimitTier)\n        if tier.contains(\"max\") {\n            return .max\n        }\n        if tier.contains(\"pro\") {\n            return .pro\n        }\n        if tier.contains(\"team\") {\n            return .team\n        }\n        if tier.contains(\"enterprise\") {\n            return .enterprise\n        }\n        return nil\n    }\n\n    private static func normalized(_ text: String?) -> String {\n        text?\n            .trimmingCharacters(in: .whitespacesAndNewlines)\n            .lowercased() ?? \"\"\n    }\n\n    private static func normalizedWords(_ text: String?) -> [String] {\n        self.normalized(text)\n            .split(whereSeparator: { !$0.isLetter && !$0.isNumber })\n            .map(String.init)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Claude/ClaudeProviderDescriptor.swift",
    "content": "import CodexBarMacroSupport\nimport Foundation\n\n@ProviderDescriptorRegistration\n@ProviderDescriptorDefinition\npublic enum ClaudeProviderDescriptor {\n    static func makeDescriptor() -> ProviderDescriptor {\n        ProviderDescriptor(\n            id: .claude,\n            metadata: ProviderMetadata(\n                id: .claude,\n                displayName: \"Claude\",\n                sessionLabel: \"Session\",\n                weeklyLabel: \"Weekly\",\n                opusLabel: \"Sonnet\",\n                supportsOpus: true,\n                supportsCredits: false,\n                creditsHint: \"\",\n                toggleTitle: \"Show Claude Code usage\",\n                cliName: \"claude\",\n                defaultEnabled: false,\n                isPrimaryProvider: true,\n                usesAccountFallback: false,\n                browserCookieOrder: ProviderBrowserCookieDefaults.defaultImportOrder,\n                dashboardURL: \"https://console.anthropic.com/settings/billing\",\n                subscriptionDashboardURL: \"https://claude.ai/settings/usage\",\n                statusPageURL: \"https://status.claude.com/\"),\n            branding: ProviderBranding(\n                iconStyle: .claude,\n                iconResourceName: \"ProviderIcon-claude\",\n                color: ProviderColor(red: 204 / 255, green: 124 / 255, blue: 94 / 255)),\n            tokenCost: ProviderTokenCostConfig(\n                supportsTokenCost: true,\n                noDataMessage: self.noDataMessage),\n            fetchPlan: ProviderFetchPlan(\n                sourceModes: [.auto, .web, .cli, .oauth],\n                pipeline: ProviderFetchPipeline(resolveStrategies: self.resolveStrategies)),\n            cli: ProviderCLIConfig(\n                name: \"claude\",\n                versionDetector: { browserDetection in\n                    ClaudeUsageFetcher(browserDetection: browserDetection).detectVersion()\n                }))\n    }\n\n    private static func resolveStrategies(context: ProviderFetchContext) async -> [any ProviderFetchStrategy] {\n        guard context.sourceMode != .api else { return [] }\n\n        let planningInput = await Self.makePlanningInput(context: context)\n        let plan = ClaudeSourcePlanner.resolve(input: planningInput)\n        let manualCookieHeader = Self.manualCookieHeader(from: context)\n\n        return plan.orderedSteps.map { step in\n            let strategy: any ProviderFetchStrategy = switch step.dataSource {\n            case .oauth:\n                ClaudeOAuthFetchStrategy()\n            case .web:\n                ClaudeWebFetchStrategy(browserDetection: context.browserDetection)\n            case .cli:\n                ClaudeCLIFetchStrategy(\n                    useWebExtras: context.runtime == .app\n                        && planningInput.webExtrasEnabled,\n                    manualCookieHeader: manualCookieHeader,\n                    browserDetection: context.browserDetection)\n            case .auto:\n                fatalError(\"Planner must not emit .auto as an executable step.\")\n            }\n            return ClaudePlannedFetchStrategy(base: strategy, plannedStep: step)\n        }\n    }\n\n    private static func makePlanningInput(context: ProviderFetchContext) async -> ClaudeSourcePlanningInput {\n        let webExtrasEnabled = context.settings?.claude?.webExtrasEnabled ?? false\n        return ClaudeSourcePlanningInput(\n            runtime: context.runtime,\n            selectedDataSource: Self.sourceDataSource(from: context.sourceMode),\n            webExtrasEnabled: webExtrasEnabled,\n            hasWebSession: ClaudeWebFetchStrategy.isAvailableForFallback(\n                context: context,\n                browserDetection: context.browserDetection),\n            hasCLI: ClaudeCLIResolver.isAvailable(environment: context.env),\n            hasOAuthCredentials: ClaudeOAuthPlanningAvailability.isAvailable(\n                runtime: context.runtime,\n                sourceMode: context.sourceMode,\n                environment: context.env))\n    }\n\n    private static func manualCookieHeader(from context: ProviderFetchContext) -> String? {\n        guard context.settings?.claude?.cookieSource == .manual else { return nil }\n        return CookieHeaderNormalizer.normalize(context.settings?.claude?.manualCookieHeader)\n    }\n\n    private static func noDataMessage() -> String {\n        \"No Claude usage logs found in ~/.config/claude/projects or ~/.claude/projects.\"\n    }\n\n    public static func resolveUsageStrategy(\n        selectedDataSource: ClaudeUsageDataSource,\n        webExtrasEnabled: Bool,\n        hasWebSession: Bool,\n        hasCLI: Bool,\n        hasOAuthCredentials: Bool) -> ClaudeUsageStrategy\n    {\n        let plan = ClaudeSourcePlanner.resolve(input: ClaudeSourcePlanningInput(\n            runtime: .app,\n            selectedDataSource: selectedDataSource,\n            webExtrasEnabled: webExtrasEnabled,\n            hasWebSession: hasWebSession,\n            hasCLI: hasCLI,\n            hasOAuthCredentials: hasOAuthCredentials))\n        return plan.compatibilityStrategy ?? ClaudeUsageStrategy(dataSource: selectedDataSource, useWebExtras: false)\n    }\n\n    private static func sourceDataSource(from mode: ProviderSourceMode) -> ClaudeUsageDataSource {\n        switch mode {\n        case .auto, .api:\n            .auto\n        case .web:\n            .web\n        case .cli:\n            .cli\n        case .oauth:\n            .oauth\n        }\n    }\n}\n\npublic struct ClaudeUsageStrategy: Equatable, Sendable {\n    public let dataSource: ClaudeUsageDataSource\n    public let useWebExtras: Bool\n}\n\npublic enum ClaudeOAuthPlanningAvailability {\n    public static func isAvailable(\n        runtime: ProviderRuntime,\n        sourceMode: ProviderSourceMode,\n        environment: [String: String]) -> Bool\n    {\n        ClaudeOAuthFetchStrategy.isPlausiblyAvailable(\n            runtime: runtime,\n            sourceMode: sourceMode,\n            environment: environment)\n    }\n}\n\nprivate struct ClaudePlannedFetchStrategy: ProviderFetchStrategy {\n    let base: any ProviderFetchStrategy\n    let plannedStep: ClaudeFetchPlanStep\n\n    var id: String {\n        self.base.id\n    }\n\n    var kind: ProviderFetchKind {\n        self.base.kind\n    }\n\n    func isAvailable(_ context: ProviderFetchContext) async -> Bool {\n        if context.sourceMode == .auto {\n            return self.plannedStep.isPlausiblyAvailable\n        }\n        return await self.base.isAvailable(context)\n    }\n\n    func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {\n        try await self.base.fetch(context)\n    }\n\n    func shouldFallback(on error: Error, context: ProviderFetchContext) -> Bool {\n        self.base.shouldFallback(on: error, context: context)\n    }\n}\n\nstruct ClaudeOAuthFetchStrategy: ProviderFetchStrategy {\n    let id: String = \"claude.oauth\"\n    let kind: ProviderFetchKind = .oauth\n\n    #if DEBUG\n    @TaskLocal static var nonInteractiveCredentialRecordOverride: ClaudeOAuthCredentialRecord?\n    @TaskLocal static var claudeCLIAvailableOverride: Bool?\n    #endif\n\n    private func loadNonInteractiveCredentialRecord(environment: [String: String]) -> ClaudeOAuthCredentialRecord? {\n        #if DEBUG\n        if let override = Self.nonInteractiveCredentialRecordOverride { return override }\n        #endif\n\n        return try? ClaudeOAuthCredentialsStore.loadRecord(\n            environment: environment,\n            allowKeychainPrompt: false,\n            respectKeychainPromptCooldown: true,\n            allowClaudeKeychainRepairWithoutPrompt: false)\n    }\n\n    private func isClaudeCLIAvailable(environment: [String: String]) -> Bool {\n        #if DEBUG\n        if let override = Self.claudeCLIAvailableOverride { return override }\n        #endif\n        return ClaudeCLIResolver.isAvailable(environment: environment)\n    }\n\n    static func isPlausiblyAvailable(\n        runtime: ProviderRuntime,\n        sourceMode: ProviderSourceMode,\n        environment: [String: String]) -> Bool\n    {\n        let strategy = ClaudeOAuthFetchStrategy()\n        let nonInteractiveRecord = strategy.loadNonInteractiveCredentialRecord(environment: environment)\n        let nonInteractiveCredentials = nonInteractiveRecord?.credentials\n        let hasRequiredScopeWithoutPrompt = nonInteractiveCredentials?.scopes.contains(\"user:profile\") == true\n        if hasRequiredScopeWithoutPrompt, nonInteractiveCredentials?.isExpired == false {\n            return true\n        }\n\n        let hasEnvironmentOAuthToken = !(environment[ClaudeOAuthCredentialsStore.environmentTokenKey]?\n            .trimmingCharacters(in: .whitespacesAndNewlines)\n            .isEmpty ?? true)\n        let claudeCLIAvailable = strategy.isClaudeCLIAvailable(environment: environment)\n\n        if hasEnvironmentOAuthToken {\n            return true\n        }\n\n        if let nonInteractiveRecord, hasRequiredScopeWithoutPrompt, nonInteractiveRecord.credentials.isExpired {\n            switch nonInteractiveRecord.owner {\n            case .codexbar:\n                let refreshToken = nonInteractiveRecord.credentials.refreshToken?\n                    .trimmingCharacters(in: .whitespacesAndNewlines) ?? \"\"\n                if sourceMode == .auto {\n                    return !refreshToken.isEmpty\n                }\n                return true\n            case .claudeCLI:\n                if sourceMode == .auto {\n                    return claudeCLIAvailable\n                }\n                return true\n            case .environment:\n                return sourceMode != .auto\n            }\n        }\n\n        guard sourceMode == .auto else { return true }\n\n        let promptPolicyApplicable = ClaudeOAuthKeychainPromptPreference.isApplicable()\n        if promptPolicyApplicable, ProviderInteractionContext.current == .userInitiated {\n            _ = ClaudeOAuthKeychainAccessGate.clearDenied()\n        }\n\n        let shouldAllowStartupBootstrap = promptPolicyApplicable &&\n            runtime == .app &&\n            ProviderRefreshContext.current == .startup &&\n            ProviderInteractionContext.current == .background &&\n            ClaudeOAuthKeychainPromptPreference.current() == .onlyOnUserAction &&\n            !ClaudeOAuthCredentialsStore.hasCachedCredentials(environment: environment)\n        if shouldAllowStartupBootstrap {\n            return ClaudeOAuthKeychainAccessGate.shouldAllowPrompt()\n        }\n\n        if promptPolicyApplicable,\n           !ClaudeOAuthKeychainAccessGate.shouldAllowPrompt()\n        {\n            return false\n        }\n        return ClaudeOAuthCredentialsStore.hasClaudeKeychainCredentialsWithoutPrompt()\n    }\n\n    func isAvailable(_ context: ProviderFetchContext) async -> Bool {\n        Self.isPlausiblyAvailable(\n            runtime: context.runtime,\n            sourceMode: context.sourceMode,\n            environment: context.env)\n    }\n\n    func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {\n        let fetcher = ClaudeUsageFetcher(\n            browserDetection: context.browserDetection,\n            environment: context.env,\n            dataSource: .oauth,\n            oauthKeychainPromptCooldownEnabled: context.sourceMode == .auto,\n            allowBackgroundDelegatedRefresh: context.runtime == .cli,\n            allowStartupBootstrapPrompt: context.runtime == .app &&\n                (context.sourceMode == .auto || context.sourceMode == .oauth),\n            useWebExtras: false)\n        let usage = try await fetcher.loadLatestUsage(model: \"sonnet\")\n        return self.makeResult(\n            usage: Self.snapshot(from: usage),\n            sourceLabel: \"oauth\")\n    }\n\n    func shouldFallback(on _: Error, context: ProviderFetchContext) -> Bool {\n        // In Auto mode, fall back to the next strategy (cli/web) if OAuth fails (e.g. user cancels keychain prompt\n        // or auth breaks).\n        context.runtime == .app && context.sourceMode == .auto\n    }\n\n    fileprivate static func snapshot(from usage: ClaudeUsageSnapshot) -> UsageSnapshot {\n        let identity = ProviderIdentitySnapshot(\n            providerID: .claude,\n            accountEmail: usage.accountEmail,\n            accountOrganization: usage.accountOrganization,\n            loginMethod: usage.loginMethod)\n        return UsageSnapshot(\n            primary: usage.primary,\n            secondary: usage.secondary,\n            tertiary: usage.opus,\n            providerCost: usage.providerCost,\n            updatedAt: usage.updatedAt,\n            identity: identity)\n    }\n}\n\nstruct ClaudeWebFetchStrategy: ProviderFetchStrategy {\n    let id: String = \"claude.web\"\n    let kind: ProviderFetchKind = .web\n    let browserDetection: BrowserDetection\n\n    func isAvailable(_ context: ProviderFetchContext) async -> Bool {\n        Self.isAvailableForFallback(context: context, browserDetection: self.browserDetection)\n    }\n\n    func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {\n        let fetcher = ClaudeUsageFetcher(\n            browserDetection: browserDetection,\n            dataSource: .web,\n            useWebExtras: false,\n            manualCookieHeader: Self.manualCookieHeader(from: context))\n        let usage = try await fetcher.loadLatestUsage(model: \"sonnet\")\n        return self.makeResult(\n            usage: ClaudeOAuthFetchStrategy.snapshot(from: usage),\n            sourceLabel: \"web\")\n    }\n\n    func shouldFallback(on error: Error, context: ProviderFetchContext) -> Bool {\n        guard context.sourceMode == .auto else { return false }\n        _ = error\n        // In CLI runtime auto mode, web comes before CLI so fallback is required.\n        // In app runtime auto mode, web is terminal and should surface its concrete error.\n        return context.runtime == .cli\n    }\n\n    fileprivate static func isAvailableForFallback(\n        context: ProviderFetchContext,\n        browserDetection: BrowserDetection) -> Bool\n    {\n        if let header = self.manualCookieHeader(from: context) {\n            return ClaudeWebAPIFetcher.hasSessionKey(cookieHeader: header)\n        }\n        guard context.settings?.claude?.cookieSource != .off else { return false }\n        return ClaudeWebAPIFetcher.hasSessionKey(browserDetection: browserDetection)\n    }\n\n    private static func manualCookieHeader(from context: ProviderFetchContext) -> String? {\n        guard context.settings?.claude?.cookieSource == .manual else { return nil }\n        return CookieHeaderNormalizer.normalize(context.settings?.claude?.manualCookieHeader)\n    }\n}\n\nstruct ClaudeCLIFetchStrategy: ProviderFetchStrategy {\n    let id: String = \"claude.cli\"\n    let kind: ProviderFetchKind = .cli\n    let useWebExtras: Bool\n    let manualCookieHeader: String?\n    let browserDetection: BrowserDetection\n\n    func isAvailable(_: ProviderFetchContext) async -> Bool {\n        true\n    }\n\n    func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {\n        let keepAlive = context.settings?.debugKeepCLISessionsAlive ?? false\n        let fetcher = ClaudeUsageFetcher(\n            browserDetection: browserDetection,\n            environment: context.env,\n            dataSource: .cli,\n            useWebExtras: self.useWebExtras,\n            manualCookieHeader: self.manualCookieHeader,\n            keepCLISessionsAlive: keepAlive)\n        let usage = try await fetcher.loadLatestUsage(model: \"sonnet\")\n        return self.makeResult(\n            usage: ClaudeOAuthFetchStrategy.snapshot(from: usage),\n            sourceLabel: \"claude\")\n    }\n\n    func shouldFallback(on _: Error, context: ProviderFetchContext) -> Bool {\n        guard context.runtime == .app, context.sourceMode == .auto else { return false }\n        // Only fall through when web is actually available; otherwise preserve actionable CLI errors.\n        return ClaudeWebFetchStrategy.isAvailableForFallback(\n            context: context,\n            browserDetection: self.browserDetection)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Claude/ClaudeSourcePlanner.swift",
    "content": "import Foundation\n\npublic struct ClaudeSourcePlanningInput: Equatable, Sendable {\n    public let runtime: ProviderRuntime\n    public let selectedDataSource: ClaudeUsageDataSource\n    public let webExtrasEnabled: Bool\n    public let hasWebSession: Bool\n    public let hasCLI: Bool\n    public let hasOAuthCredentials: Bool\n\n    public init(\n        runtime: ProviderRuntime,\n        selectedDataSource: ClaudeUsageDataSource,\n        webExtrasEnabled: Bool,\n        hasWebSession: Bool,\n        hasCLI: Bool,\n        hasOAuthCredentials: Bool)\n    {\n        self.runtime = runtime\n        self.selectedDataSource = selectedDataSource\n        self.webExtrasEnabled = webExtrasEnabled\n        self.hasWebSession = hasWebSession\n        self.hasCLI = hasCLI\n        self.hasOAuthCredentials = hasOAuthCredentials\n    }\n}\n\npublic enum ClaudeSourcePlanReason: String, Equatable, Sendable {\n    case explicitSourceSelection = \"explicit-source-selection\"\n    case appAutoPreferredOAuth = \"app-auto-preferred-oauth\"\n    case appAutoFallbackCLI = \"app-auto-fallback-cli\"\n    case appAutoFallbackWeb = \"app-auto-fallback-web\"\n    case cliAutoPreferredWeb = \"cli-auto-preferred-web\"\n    case cliAutoFallbackCLI = \"cli-auto-fallback-cli\"\n}\n\npublic struct ClaudeFetchPlanStep: Equatable, Sendable {\n    public let dataSource: ClaudeUsageDataSource\n    public let inclusionReason: ClaudeSourcePlanReason\n    public let isPlausiblyAvailable: Bool\n\n    public init(\n        dataSource: ClaudeUsageDataSource,\n        inclusionReason: ClaudeSourcePlanReason,\n        isPlausiblyAvailable: Bool)\n    {\n        self.dataSource = dataSource\n        self.inclusionReason = inclusionReason\n        self.isPlausiblyAvailable = isPlausiblyAvailable\n    }\n}\n\npublic struct ClaudeFetchPlan: Equatable, Sendable {\n    public let input: ClaudeSourcePlanningInput\n    public let orderedSteps: [ClaudeFetchPlanStep]\n\n    public init(input: ClaudeSourcePlanningInput, orderedSteps: [ClaudeFetchPlanStep]) {\n        self.input = input\n        self.orderedSteps = orderedSteps\n    }\n\n    public var availableSteps: [ClaudeFetchPlanStep] {\n        self.orderedSteps.filter(\\.isPlausiblyAvailable)\n    }\n\n    public var isNoSourceAvailable: Bool {\n        self.availableSteps.isEmpty\n    }\n\n    public var preferredStep: ClaudeFetchPlanStep? {\n        switch self.input.selectedDataSource {\n        case .auto:\n            self.availableSteps.first\n        case .oauth, .web, .cli:\n            self.orderedSteps.first\n        }\n    }\n\n    public var executionSteps: [ClaudeFetchPlanStep] {\n        switch self.input.selectedDataSource {\n        case .auto:\n            self.availableSteps\n        case .oauth, .web, .cli:\n            self.orderedSteps\n        }\n    }\n\n    public var compatibilityStrategy: ClaudeUsageStrategy? {\n        guard let preferredStep else { return nil }\n        let useWebExtras = self.input.runtime == .app\n            && preferredStep.dataSource == .cli\n            && self.input.webExtrasEnabled\n        return ClaudeUsageStrategy(\n            dataSource: preferredStep.dataSource,\n            useWebExtras: useWebExtras)\n    }\n\n    public var orderLabel: String {\n        self.orderedSteps.map(\\.dataSource.sourceLabel).joined(separator: \"→\")\n    }\n\n    public func debugLines() -> [String] {\n        var lines = [\"planner_order=\\(self.orderLabel)\"]\n        lines.append(\"planner_selected=\\(self.preferredStep?.dataSource.rawValue ?? \"none\")\")\n        lines.append(\"planner_no_source=\\(self.isNoSourceAvailable)\")\n        for step in self.orderedSteps {\n            let availability = step.isPlausiblyAvailable ? \"available\" : \"unavailable\"\n            lines.append(\n                \"planner_step.\\(step.dataSource.rawValue)=\\(availability) reason=\\(step.inclusionReason.rawValue)\")\n        }\n        return lines\n    }\n}\n\npublic enum ClaudeCLIResolver {\n    #if DEBUG\n    @TaskLocal static var resolvedBinaryPathOverrideForTesting: String?\n\n    public static var currentResolvedBinaryPathOverrideForTesting: String? {\n        self.resolvedBinaryPathOverrideForTesting\n    }\n\n    public static func withResolvedBinaryPathOverrideForTesting<T>(\n        _ path: String?,\n        operation: () async throws -> T) async rethrows -> T\n    {\n        try await self.$resolvedBinaryPathOverrideForTesting.withValue(path) {\n            try await operation()\n        }\n    }\n    #endif\n\n    public static func resolvedBinaryPath(\n        environment: [String: String] = ProcessInfo.processInfo.environment,\n        loginPATH: [String]? = LoginShellPathCache.shared.current)\n        -> String?\n    {\n        #if DEBUG\n        if let override = self.resolvedBinaryPathOverrideForTesting {\n            return FileManager.default.isExecutableFile(atPath: override) ? override : nil\n        }\n        #endif\n\n        var normalizedEnvironment = environment\n        if let override = environment[\"CLAUDE_CLI_PATH\"]?.trimmingCharacters(in: .whitespacesAndNewlines) {\n            if override.isEmpty {\n                normalizedEnvironment.removeValue(forKey: \"CLAUDE_CLI_PATH\")\n            } else {\n                normalizedEnvironment[\"CLAUDE_CLI_PATH\"] = override\n                if FileManager.default.isExecutableFile(atPath: override) {\n                    return override\n                }\n            }\n        }\n\n        return BinaryLocator.resolveClaudeBinary(\n            env: normalizedEnvironment,\n            loginPATH: loginPATH)\n    }\n\n    public static func isAvailable(environment: [String: String] = ProcessInfo.processInfo.environment) -> Bool {\n        self.resolvedBinaryPath(environment: environment) != nil\n    }\n}\n\npublic enum ClaudeSourcePlanner {\n    public static func resolve(input: ClaudeSourcePlanningInput) -> ClaudeFetchPlan {\n        ClaudeFetchPlan(input: input, orderedSteps: self.makeSteps(input: input))\n    }\n\n    private static func makeSteps(input: ClaudeSourcePlanningInput) -> [ClaudeFetchPlanStep] {\n        switch input.selectedDataSource {\n        case .auto:\n            switch input.runtime {\n            case .app:\n                [\n                    self.step(.oauth, reason: .appAutoPreferredOAuth, input: input),\n                    self.step(.cli, reason: .appAutoFallbackCLI, input: input),\n                    self.step(.web, reason: .appAutoFallbackWeb, input: input),\n                ]\n            case .cli:\n                [\n                    self.step(.web, reason: .cliAutoPreferredWeb, input: input),\n                    self.step(.cli, reason: .cliAutoFallbackCLI, input: input),\n                ]\n            }\n        case .oauth:\n            [self.step(.oauth, reason: .explicitSourceSelection, input: input)]\n        case .web:\n            [self.step(.web, reason: .explicitSourceSelection, input: input)]\n        case .cli:\n            [self.step(.cli, reason: .explicitSourceSelection, input: input)]\n        }\n    }\n\n    private static func step(\n        _ dataSource: ClaudeUsageDataSource,\n        reason: ClaudeSourcePlanReason,\n        input: ClaudeSourcePlanningInput) -> ClaudeFetchPlanStep\n    {\n        ClaudeFetchPlanStep(\n            dataSource: dataSource,\n            inclusionReason: reason,\n            isPlausiblyAvailable: self.isPlausiblyAvailable(dataSource, input: input))\n    }\n\n    private static func isPlausiblyAvailable(\n        _ dataSource: ClaudeUsageDataSource,\n        input: ClaudeSourcePlanningInput) -> Bool\n    {\n        switch dataSource {\n        case .auto:\n            false\n        case .oauth:\n            input.hasOAuthCredentials\n        case .web:\n            input.hasWebSession\n        case .cli:\n            input.hasCLI\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Claude/ClaudeStatusProbe.swift",
    "content": "import Foundation\n\npublic struct ClaudeStatusSnapshot: Sendable {\n    public let sessionPercentLeft: Int?\n    public let weeklyPercentLeft: Int?\n    public let opusPercentLeft: Int?\n    public let accountEmail: String?\n    public let accountOrganization: String?\n    public let loginMethod: String?\n    public let primaryResetDescription: String?\n    public let secondaryResetDescription: String?\n    public let opusResetDescription: String?\n    public let rawText: String\n}\n\npublic struct ClaudeAccountIdentity: Sendable {\n    public let accountEmail: String?\n    public let accountOrganization: String?\n    public let loginMethod: String?\n\n    public init(accountEmail: String?, accountOrganization: String?, loginMethod: String?) {\n        self.accountEmail = accountEmail\n        self.accountOrganization = accountOrganization\n        self.loginMethod = loginMethod\n    }\n}\n\npublic enum ClaudeStatusProbeError: LocalizedError, Sendable {\n    case claudeNotInstalled\n    case parseFailed(String)\n    case timedOut\n\n    public var errorDescription: String? {\n        switch self {\n        case .claudeNotInstalled:\n            \"Claude CLI is not installed or not on PATH.\"\n        case let .parseFailed(msg):\n            \"Could not parse Claude usage: \\(msg)\"\n        case .timedOut:\n            \"Claude usage probe timed out.\"\n        }\n    }\n}\n\n/// Runs `claude` inside a PTY, sends `/usage`, and parses the rendered text panel.\npublic struct ClaudeStatusProbe: Sendable {\n    public var claudeBinary: String = \"claude\"\n    public var timeout: TimeInterval = 20.0\n    public var keepCLISessionsAlive: Bool = false\n    private static let log = CodexBarLog.logger(LogCategories.claudeProbe)\n    #if DEBUG\n    public typealias FetchOverride = @Sendable (String, TimeInterval, Bool) async throws -> ClaudeStatusSnapshot\n    @TaskLocal static var fetchOverride: FetchOverride?\n    #endif\n\n    public init(claudeBinary: String = \"claude\", timeout: TimeInterval = 20.0, keepCLISessionsAlive: Bool = false) {\n        self.claudeBinary = claudeBinary\n        self.timeout = timeout\n        self.keepCLISessionsAlive = keepCLISessionsAlive\n    }\n\n    #if DEBUG\n    public static var currentFetchOverrideForTesting: FetchOverride? {\n        self.fetchOverride\n    }\n\n    public static func withFetchOverrideForTesting<T>(\n        _ override: FetchOverride?,\n        operation: () async throws -> T) async rethrows -> T\n    {\n        try await self.$fetchOverride.withValue(override) {\n            try await operation()\n        }\n    }\n\n    public static func withFetchOverrideForTesting<T>(\n        _ override: FetchOverride?,\n        operation: () async -> T) async -> T\n    {\n        await self.$fetchOverride.withValue(override) {\n            await operation()\n        }\n    }\n    #endif\n\n    public func fetch() async throws -> ClaudeStatusSnapshot {\n        let resolved = Self.resolvedBinaryPath(binaryName: self.claudeBinary)\n        guard let resolved, Self.isBinaryAvailable(resolved) else {\n            throw ClaudeStatusProbeError.claudeNotInstalled\n        }\n\n        // Run commands sequentially through a shared Claude session to avoid warm-up churn.\n        let timeout = self.timeout\n        let keepAlive = self.keepCLISessionsAlive\n        #if DEBUG\n        if let override = Self.fetchOverride {\n            return try await override(resolved, timeout, keepAlive)\n        }\n        #endif\n        do {\n            var usage = try await Self.capture(subcommand: \"/usage\", binary: resolved, timeout: timeout)\n            if !Self.usageOutputLooksRelevant(usage) {\n                Self.log.debug(\"Claude CLI /usage looked like startup output; retrying once\")\n                usage = try await Self.capture(subcommand: \"/usage\", binary: resolved, timeout: max(timeout, 14))\n            }\n            let status = try? await Self.capture(subcommand: \"/status\", binary: resolved, timeout: min(timeout, 12))\n            let snap = try Self.parse(text: usage, statusText: status)\n\n            Self.log.info(\"Claude CLI scrape ok\", metadata: [\n                \"sessionPercentLeft\": \"\\(snap.sessionPercentLeft ?? -1)\",\n                \"weeklyPercentLeft\": \"\\(snap.weeklyPercentLeft ?? -1)\",\n                \"opusPercentLeft\": \"\\(snap.opusPercentLeft ?? -1)\",\n            ])\n            if !keepAlive {\n                await ClaudeCLISession.shared.reset()\n            }\n            return snap\n        } catch {\n            if !keepAlive {\n                await ClaudeCLISession.shared.reset()\n            }\n            throw error\n        }\n    }\n\n    // MARK: - Parsing helpers\n\n    private struct LabelSearchContext {\n        let lines: [String]\n        let normalizedLines: [String]\n        let normalizedData: Data\n\n        init(text: String) {\n            self.lines = text.components(separatedBy: .newlines)\n            self.normalizedLines = self.lines.map { ClaudeStatusProbe.normalizedForLabelSearch($0) }\n            let normalized = ClaudeStatusProbe.normalizedForLabelSearch(text)\n            self.normalizedData = Data(normalized.utf8)\n        }\n\n        func contains(_ needle: String) -> Bool {\n            self.normalizedData.range(of: Data(needle.utf8)) != nil\n        }\n    }\n\n    public static func parse(text: String, statusText: String? = nil) throws -> ClaudeStatusSnapshot {\n        let clean = TextParsing.stripANSICodes(text)\n        let statusClean = statusText.map(TextParsing.stripANSICodes)\n        guard !clean.isEmpty else { throw ClaudeStatusProbeError.timedOut }\n\n        let shouldDump = ProcessInfo.processInfo.environment[\"DEBUG_CLAUDE_DUMP\"] == \"1\"\n\n        if let usageError = self.extractUsageError(text: clean) {\n            Self.dumpIfNeeded(\n                enabled: shouldDump,\n                reason: \"usageError: \\(usageError)\",\n                usage: clean,\n                status: statusText)\n            throw ClaudeStatusProbeError.parseFailed(usageError)\n        }\n\n        // Claude CLI renders /usage as a TUI. Our PTY capture includes earlier screen fragments (including a status\n        // line\n        // with a \"0%\" context meter) before the usage panel is drawn. To keep parsing stable, trim to the last\n        // Settings/Usage panel when present.\n        let usagePanelText = self.trimToLatestUsagePanel(clean) ?? clean\n        let labelContext = LabelSearchContext(text: usagePanelText)\n\n        var sessionPct = self.extractPercent(labelSubstring: \"Current session\", context: labelContext)\n        var weeklyPct = self.extractPercent(labelSubstring: \"Current week (all models)\", context: labelContext)\n        var opusPct = self.extractPercent(\n            labelSubstrings: [\n                \"Current week (Opus)\",\n                \"Current week (Sonnet only)\",\n                \"Current week (Sonnet)\",\n            ],\n            context: labelContext)\n\n        // Fallback: order-based percent scraping when labels are present but the surrounding layout moved.\n        // Only apply the fallback when the corresponding label exists in the rendered panel; enterprise accounts\n        // may omit the weekly panel entirely, and we should treat that as \"unavailable\" rather than guessing.\n        let compactContext = usagePanelText.lowercased().filter { !$0.isWhitespace }\n        let hasWeeklyLabel =\n            labelContext.contains(\"currentweek\")\n            || compactContext.contains(\"currentweek\")\n        let hasOpusLabel = labelContext.contains(\"opus\") || labelContext.contains(\"sonnet\")\n\n        if sessionPct == nil || (hasWeeklyLabel && weeklyPct == nil) || (hasOpusLabel && opusPct == nil) {\n            let ordered = self.allPercents(usagePanelText)\n            if sessionPct == nil, ordered.indices.contains(0) { sessionPct = ordered[0] }\n            if hasWeeklyLabel, weeklyPct == nil, ordered.indices.contains(1) { weeklyPct = ordered[1] }\n            if hasOpusLabel, opusPct == nil, ordered.indices.contains(2) { opusPct = ordered[2] }\n        }\n\n        let identity = Self.parseIdentity(usageText: clean, statusText: statusClean)\n\n        guard let sessionPct else {\n            Self.dumpIfNeeded(\n                enabled: shouldDump,\n                reason: \"missing session label\",\n                usage: clean,\n                status: statusText)\n            if shouldDump {\n                let tail = usagePanelText.suffix(1800)\n                let snippet = tail.isEmpty ? \"(empty)\" : String(tail)\n                throw ClaudeStatusProbeError.parseFailed(\n                    \"Missing Current session.\\n\\n--- Clean usage tail ---\\n\\(snippet)\")\n            }\n            throw ClaudeStatusProbeError.parseFailed(\"Missing Current session.\")\n        }\n\n        let sessionReset = self.extractReset(labelSubstring: \"Current session\", context: labelContext)\n        let weeklyReset = hasWeeklyLabel\n            ? self.extractReset(labelSubstring: \"Current week (all models)\", context: labelContext)\n            : nil\n        let opusReset = hasOpusLabel\n            ? self.extractReset(\n                labelSubstrings: [\n                    \"Current week (Opus)\",\n                    \"Current week (Sonnet only)\",\n                    \"Current week (Sonnet)\",\n                ],\n                context: labelContext)\n            : nil\n\n        return ClaudeStatusSnapshot(\n            sessionPercentLeft: sessionPct,\n            weeklyPercentLeft: weeklyPct,\n            opusPercentLeft: opusPct,\n            accountEmail: identity.accountEmail,\n            accountOrganization: identity.accountOrganization,\n            loginMethod: identity.loginMethod,\n            primaryResetDescription: sessionReset,\n            secondaryResetDescription: weeklyReset,\n            opusResetDescription: opusReset,\n            rawText: text + (statusText ?? \"\"))\n    }\n\n    public static func parseIdentity(usageText: String?, statusText: String?) -> ClaudeAccountIdentity {\n        let usageClean = usageText.map(TextParsing.stripANSICodes) ?? \"\"\n        let statusClean = statusText.map(TextParsing.stripANSICodes)\n        return self.extractIdentity(usageText: usageClean, statusText: statusClean)\n    }\n\n    public static func fetchIdentity(\n        timeout: TimeInterval = 12.0,\n        environment: [String: String] = ProcessInfo.processInfo.environment) async throws -> ClaudeAccountIdentity\n    {\n        let resolved = self.resolvedBinaryPath(binaryName: \"claude\", environment: environment)\n        guard let resolved, self.isBinaryAvailable(resolved) else {\n            throw ClaudeStatusProbeError.claudeNotInstalled\n        }\n        let statusText = try await Self.capture(subcommand: \"/status\", binary: resolved, timeout: timeout)\n        return Self.parseIdentity(usageText: nil, statusText: statusText)\n    }\n\n    public static func touchOAuthAuthPath(\n        timeout: TimeInterval = 8,\n        environment: [String: String] = ProcessInfo.processInfo.environment) async throws\n    {\n        let resolved = self.resolvedBinaryPath(binaryName: \"claude\", environment: environment)\n        guard let resolved, self.isBinaryAvailable(resolved) else {\n            throw ClaudeStatusProbeError.claudeNotInstalled\n        }\n        do {\n            // Use a more robust capture configuration than the standard `/status` scrape:\n            // - Avoid the short idle-timeout which can terminate the session while CLI auth checks are still running.\n            // - We intentionally do not parse output here; success is \"the command ran without timing out\".\n            _ = try await ClaudeCLISession.shared.capture(\n                subcommand: \"/status\",\n                binary: resolved,\n                timeout: timeout,\n                idleTimeout: nil,\n                stopOnSubstrings: [],\n                settleAfterStop: 0.8,\n                sendEnterEvery: 0.8)\n            await ClaudeCLISession.shared.reset()\n        } catch {\n            await ClaudeCLISession.shared.reset()\n            throw error\n        }\n    }\n\n    public static func isClaudeBinaryAvailable(\n        environment: [String: String] = ProcessInfo.processInfo.environment) -> Bool\n    {\n        let resolved = self.resolvedBinaryPath(binaryName: \"claude\", environment: environment)\n        return self.isBinaryAvailable(resolved)\n    }\n\n    private static func extractPercent(labelSubstring: String, context: LabelSearchContext) -> Int? {\n        let lines = context.lines\n        let label = self.normalizedForLabelSearch(labelSubstring)\n        for (idx, normalizedLine) in context.normalizedLines.enumerated() where normalizedLine.contains(label) {\n            // Claude's usage panel can take a moment to render percentages (especially on enterprise accounts),\n            // so scan a larger window than the original 3–4 lines.\n            let window = lines.dropFirst(idx).prefix(12)\n            for candidate in window {\n                if let pct = self.percentFromLine(candidate) { return pct }\n            }\n        }\n        return nil\n    }\n\n    private static func usageOutputLooksRelevant(_ text: String) -> Bool {\n        let normalized = TextParsing.stripANSICodes(text).lowercased().filter { !$0.isWhitespace }\n        return normalized.contains(\"currentsession\")\n            || normalized.contains(\"currentweek\")\n            || normalized.contains(\"loadingusage\")\n            || normalized.contains(\"failedtoloadusagedata\")\n    }\n\n    private static func extractPercent(labelSubstrings: [String], context: LabelSearchContext) -> Int? {\n        for label in labelSubstrings {\n            if let value = self.extractPercent(labelSubstring: label, context: context) { return value }\n        }\n        return nil\n    }\n\n    private static func percentFromLine(_ line: String, assumeRemainingWhenUnclear: Bool = false) -> Int? {\n        if self.isLikelyStatusContextLine(line) { return nil }\n\n        // Allow optional Unicode whitespace before % to handle CLI formatting changes.\n        let pattern = #\"([0-9]{1,3}(?:\\.[0-9]+)?)\\p{Zs}*%\"#\n        guard let regex = try? NSRegularExpression(pattern: pattern, options: [.caseInsensitive]) else { return nil }\n        let range = NSRange(line.startIndex..<line.endIndex, in: line)\n        guard let match = regex.firstMatch(in: line, options: [], range: range),\n              match.numberOfRanges >= 2,\n              let valRange = Range(match.range(at: 1), in: line)\n        else { return nil }\n        let rawVal = Double(line[valRange]) ?? 0\n        let clamped = max(0, min(100, rawVal))\n        let lower = line.lowercased()\n        let usedKeywords = [\"used\", \"spent\", \"consumed\"]\n        let remainingKeywords = [\"left\", \"remaining\", \"available\"]\n        if usedKeywords.contains(where: lower.contains) {\n            return Int(max(0, min(100, 100 - clamped)).rounded())\n        }\n        if remainingKeywords.contains(where: lower.contains) {\n            return Int(clamped.rounded())\n        }\n        return assumeRemainingWhenUnclear ? Int(clamped.rounded()) : nil\n    }\n\n    private static func isLikelyStatusContextLine(_ line: String) -> Bool {\n        guard line.contains(\"|\") else { return false }\n        let lower = line.lowercased()\n        let modelTokens = [\"opus\", \"sonnet\", \"haiku\", \"default\"]\n        return modelTokens.contains(where: lower.contains)\n    }\n\n    private static func extractFirst(pattern: String, text: String) -> String? {\n        guard let regex = try? NSRegularExpression(pattern: pattern, options: [.caseInsensitive]) else { return nil }\n        let range = NSRange(text.startIndex..<text.endIndex, in: text)\n        guard let match = regex.firstMatch(in: text, options: [], range: range),\n              match.numberOfRanges >= 2,\n              let r = Range(match.range(at: 1), in: text) else { return nil }\n        return String(text[r]).trimmingCharacters(in: .whitespacesAndNewlines)\n    }\n\n    private static func extractIdentity(usageText: String, statusText: String?) -> ClaudeAccountIdentity {\n        let emailPatterns = [\n            #\"(?i)Account:\\s+([^\\s@]+@[^\\s@]+)\"#,\n            #\"(?i)Email:\\s+([^\\s@]+@[^\\s@]+)\"#,\n        ]\n        let looseEmailPatterns = [\n            #\"(?i)Account:\\s+(\\S+)\"#,\n            #\"(?i)Email:\\s+(\\S+)\"#,\n        ]\n        let email = emailPatterns\n            .compactMap { self.extractFirst(pattern: $0, text: usageText) }\n            .first\n            ?? emailPatterns\n            .compactMap { self.extractFirst(pattern: $0, text: statusText ?? \"\") }\n            .first\n            ?? looseEmailPatterns\n            .compactMap { self.extractFirst(pattern: $0, text: usageText) }\n            .first\n            ?? looseEmailPatterns\n            .compactMap { self.extractFirst(pattern: $0, text: statusText ?? \"\") }\n            .first\n            ?? self.extractFirst(\n                pattern: #\"(?i)[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}\"#,\n                text: usageText)\n            ?? self.extractFirst(\n                pattern: #\"(?i)[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}\"#,\n                text: statusText ?? \"\")\n        let orgPatterns = [\n            #\"(?i)Org:\\s*(.+)\"#,\n            #\"(?i)Organization:\\s*(.+)\"#,\n        ]\n        let orgRaw = orgPatterns\n            .compactMap { self.extractFirst(pattern: $0, text: usageText) }\n            .first\n            ?? orgPatterns\n            .compactMap { self.extractFirst(pattern: $0, text: statusText ?? \"\") }\n            .first\n        let org: String? = {\n            guard let orgText = orgRaw?.trimmingCharacters(in: .whitespacesAndNewlines), !orgText.isEmpty else {\n                return nil\n            }\n            // Suppress org if it’s just the email prefix (common in CLI panels).\n            if let email, orgText.lowercased().hasPrefix(email.lowercased()) { return nil }\n            return orgText\n        }()\n        // Prefer explicit login method from /status, then fall back to /usage header heuristics.\n        let login = self.extractLoginMethod(text: statusText ?? \"\") ?? self.extractLoginMethod(text: usageText)\n        return ClaudeAccountIdentity(accountEmail: email, accountOrganization: org, loginMethod: login)\n    }\n\n    private static func extractUsageError(text: String) -> String? {\n        if let jsonHint = self.extractUsageErrorJSON(text: text) { return jsonHint }\n\n        let lower = text.lowercased()\n        let compact = lower.filter { !$0.isWhitespace }\n        if lower.contains(\"do you trust the files in this folder?\"), !lower.contains(\"current session\") {\n            let folder = self.extractFirst(\n                pattern: #\"Do you trust the files in this folder\\?\\s*(?:\\r?\\n)+\\s*([^\\r\\n]+)\"#,\n                text: text)\n            let folderHint = folder.flatMap { value in\n                let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)\n                return trimmed.isEmpty ? nil : trimmed\n            }\n            if let folderHint {\n                return \"\"\"\n                Claude CLI is waiting for a folder trust prompt (\\(folderHint)). CodexBar tries to auto-accept this, \\\n                but if it keeps appearing run: `cd \"\\(folderHint)\" && claude` and choose “Yes, proceed”, then retry.\n                \"\"\"\n            }\n            return \"\"\"\n            Claude CLI is waiting for a folder trust prompt. CodexBar tries to auto-accept this, but if it keeps \\\n            appearing open `claude` once, choose “Yes, proceed”, then retry.\n            \"\"\"\n        }\n        if lower.contains(\"token_expired\") || lower.contains(\"token has expired\") {\n            return \"Claude CLI token expired. Run `claude login` to refresh.\"\n        }\n        if lower.contains(\"authentication_error\") {\n            return \"Claude CLI authentication error. Run `claude login`.\"\n        }\n        if lower.contains(\"rate_limit_error\")\n            || lower.contains(\"rate limited\")\n            || compact.contains(\"ratelimited\")\n        {\n            return \"Claude CLI usage endpoint is rate limited right now. Please try again later.\"\n        }\n        if lower.contains(\"failed to load usage data\") {\n            return \"Claude CLI could not load usage data. Open the CLI and retry `/usage`.\"\n        }\n        if compact.contains(\"failedtoloadusagedata\") {\n            return \"Claude CLI could not load usage data. Open the CLI and retry `/usage`.\"\n        }\n        return nil\n    }\n\n    /// Collect remaining percentages in the order they appear; used as a backup when labels move/rename.\n    private static func allPercents(_ text: String) -> [Int] {\n        let lines = text.components(separatedBy: .newlines)\n        let normalized = text.lowercased().filter { !$0.isWhitespace }\n        let hasUsageWindows = normalized.contains(\"currentsession\") || normalized.contains(\"currentweek\")\n        let hasLoading = normalized.contains(\"loadingusage\")\n        let hasUsagePercentKeywords = normalized.contains(\"used\") || normalized.contains(\"left\")\n            || normalized.contains(\"remaining\") || normalized.contains(\"available\")\n        let loadingOnly = hasLoading && !hasUsageWindows\n        guard hasUsageWindows || hasLoading else { return [] }\n        if loadingOnly { return [] }\n        guard hasUsagePercentKeywords else { return [] }\n\n        // Keep this strict to avoid matching Claude's status-line context meter (e.g. \"0%\") as session usage when the\n        // /usage panel is still rendering.\n        return lines.compactMap { self.percentFromLine($0, assumeRemainingWhenUnclear: false) }\n    }\n\n    /// Attempts to isolate the most recent /usage panel output from a PTY capture.\n    /// The Claude TUI draws a \"Settings: … Usage …\" header; we slice from its last occurrence to avoid earlier screen\n    /// fragments (like the status bar) contaminating percent scraping.\n    private static func trimToLatestUsagePanel(_ text: String) -> String? {\n        guard let settingsRange = text.range(of: \"Settings:\", options: [.caseInsensitive, .backwards]) else {\n            return nil\n        }\n        let tail = text[settingsRange.lowerBound...]\n        guard tail.range(of: \"Usage\", options: .caseInsensitive) != nil else { return nil }\n        let lower = tail.lowercased()\n        let hasPercent = lower.contains(\"%\")\n        let hasUsageWords = lower.contains(\"used\") || lower.contains(\"left\") || lower.contains(\"remaining\")\n            || lower.contains(\"available\")\n        let hasLoading = lower.contains(\"loading usage\")\n        guard (hasPercent && hasUsageWords) || hasLoading else { return nil }\n        return String(tail)\n    }\n\n    private static func extractReset(labelSubstring: String, context: LabelSearchContext) -> String? {\n        let lines = context.lines\n        let label = self.normalizedForLabelSearch(labelSubstring)\n        for (idx, normalizedLine) in context.normalizedLines.enumerated() where normalizedLine.contains(label) {\n            let window = lines.dropFirst(idx).prefix(14)\n            for candidate in window {\n                let trimmed = candidate.trimmingCharacters(in: .whitespacesAndNewlines)\n                let normalized = self.normalizedForLabelSearch(trimmed)\n                if normalized.hasPrefix(\"current\"), !normalized.contains(label) { break }\n                if let reset = self.resetFromLine(candidate) { return reset }\n            }\n        }\n        return nil\n    }\n\n    private static func extractReset(labelSubstrings: [String], context: LabelSearchContext) -> String? {\n        for label in labelSubstrings {\n            if let value = self.extractReset(labelSubstring: label, context: context) { return value }\n        }\n        return nil\n    }\n\n    private static func resetFromLine(_ line: String) -> String? {\n        guard let range = line.range(of: \"Resets\", options: [.caseInsensitive]) else { return nil }\n        let raw = String(line[range.lowerBound...]).trimmingCharacters(in: .whitespacesAndNewlines)\n        return self.cleanResetLine(raw)\n    }\n\n    private static func normalizedForLabelSearch(_ text: String) -> String {\n        String(text.lowercased().unicodeScalars.filter(CharacterSet.alphanumerics.contains))\n    }\n\n    /// Capture all \"Resets ...\" strings to surface in the menu.\n    private static func allResets(_ text: String) -> [String] {\n        let pat = #\"Resets[^\\r\\n]*\"#\n        guard let regex = try? NSRegularExpression(pattern: pat, options: [.caseInsensitive]) else { return [] }\n        let nsrange = NSRange(text.startIndex..<text.endIndex, in: text)\n        var results: [String] = []\n        regex.enumerateMatches(in: text, options: [], range: nsrange) { match, _, _ in\n            guard let match,\n                  let r = Range(match.range(at: 0), in: text) else { return }\n            let raw = String(text[r]).trimmingCharacters(in: .whitespacesAndNewlines)\n            results.append(self.cleanResetLine(raw))\n        }\n        return results\n    }\n\n    private static func cleanResetLine(_ raw: String) -> String {\n        // TTY capture sometimes appends a stray \")\" at line ends; trim it to keep snapshots stable.\n        var cleaned = raw.trimmingCharacters(in: .whitespacesAndNewlines)\n        cleaned = cleaned.trimmingCharacters(in: CharacterSet(charactersIn: \" )\"))\n        let openCount = cleaned.count(where: { $0 == \"(\" })\n        let closeCount = cleaned.count(where: { $0 == \")\" })\n        if openCount > closeCount { cleaned.append(\")\") }\n        return cleaned\n    }\n\n    /// Attempts to parse a Claude reset string into a Date, using the current year and handling optional timezones.\n    public static func parseResetDate(from text: String?, now: Date = .init()) -> Date? {\n        guard let normalized = self.normalizeResetInput(text) else { return nil }\n        let (raw, timeZone) = normalized\n\n        let formatter = DateFormatter()\n        formatter.locale = Locale(identifier: \"en_US_POSIX\")\n        formatter.timeZone = timeZone ?? TimeZone.current\n        formatter.defaultDate = now\n        var calendar = Calendar(identifier: .gregorian)\n        calendar.timeZone = formatter.timeZone\n\n        if let date = self.parseDate(raw, formats: Self.resetDateTimeWithMinutes, formatter: formatter) {\n            var comps = calendar.dateComponents([.year, .month, .day, .hour, .minute], from: date)\n            comps.second = 0\n            return calendar.date(from: comps)\n        }\n        if let date = self.parseDate(raw, formats: Self.resetDateTimeHourOnly, formatter: formatter) {\n            var comps = calendar.dateComponents([.year, .month, .day, .hour], from: date)\n            comps.minute = 0\n            comps.second = 0\n            return calendar.date(from: comps)\n        }\n\n        if let time = self.parseDate(raw, formats: Self.resetTimeWithMinutes, formatter: formatter) {\n            let comps = calendar.dateComponents([.hour, .minute], from: time)\n            guard let anchored = calendar.date(\n                bySettingHour: comps.hour ?? 0,\n                minute: comps.minute ?? 0,\n                second: 0,\n                of: now) else { return nil }\n            if anchored >= now { return anchored }\n            return calendar.date(byAdding: .day, value: 1, to: anchored)\n        }\n\n        guard let time = self.parseDate(raw, formats: Self.resetTimeHourOnly, formatter: formatter) else { return nil }\n        let comps = calendar.dateComponents([.hour], from: time)\n        guard let anchored = calendar.date(\n            bySettingHour: comps.hour ?? 0,\n            minute: 0,\n            second: 0,\n            of: now) else { return nil }\n        if anchored >= now { return anchored }\n        return calendar.date(byAdding: .day, value: 1, to: anchored)\n    }\n\n    private static let resetTimeWithMinutes = [\"h:mma\", \"h:mm a\", \"HH:mm\", \"H:mm\"]\n    private static let resetTimeHourOnly = [\"ha\", \"h a\"]\n\n    private static let resetDateTimeWithMinutes = [\n        \"MMM d, h:mma\",\n        \"MMM d, h:mm a\",\n        \"MMM d h:mma\",\n        \"MMM d h:mm a\",\n        \"MMM d, HH:mm\",\n        \"MMM d HH:mm\",\n    ]\n\n    private static let resetDateTimeHourOnly = [\n        \"MMM d, ha\",\n        \"MMM d, h a\",\n        \"MMM d ha\",\n        \"MMM d h a\",\n    ]\n\n    private static func normalizeResetInput(_ text: String?) -> (String, TimeZone?)? {\n        guard var raw = text?.trimmingCharacters(in: .whitespacesAndNewlines), !raw.isEmpty else { return nil }\n        raw = raw.replacingOccurrences(of: #\"(?i)^resets?:?\\s*\"#, with: \"\", options: .regularExpression)\n        raw = raw.replacingOccurrences(of: \" at \", with: \" \", options: .caseInsensitive)\n        raw = raw.replacingOccurrences(of: #\"(?i)\\b([A-Za-z]{3})(\\d)\"#, with: \"$1 $2\", options: .regularExpression)\n        raw = raw.replacingOccurrences(of: #\",(\\d)\"#, with: \", $1\", options: .regularExpression)\n        raw = raw.replacingOccurrences(of: #\"(?i)(\\d)at(?=\\d)\"#, with: \"$1 \", options: .regularExpression)\n        raw = raw.replacingOccurrences(\n            of: #\"(?<=\\d)\\.(\\d{2})\\b\"#,\n            with: \":$1\",\n            options: .regularExpression)\n\n        let timeZone = self.extractTimeZone(from: &raw)\n        raw = raw.replacingOccurrences(of: #\"\\s+\"#, with: \" \", options: .regularExpression)\n            .trimmingCharacters(in: .whitespacesAndNewlines)\n        return raw.isEmpty ? nil : (raw, timeZone)\n    }\n\n    private static func extractTimeZone(from text: inout String) -> TimeZone? {\n        guard let tzRange = text.range(of: #\"\\(([^)]+)\\)\"#, options: .regularExpression) else { return nil }\n        let tzID = String(text[tzRange]).trimmingCharacters(in: CharacterSet(charactersIn: \"() \"))\n        text.removeSubrange(tzRange)\n        text = text.trimmingCharacters(in: .whitespacesAndNewlines)\n        return TimeZone(identifier: tzID)\n    }\n\n    private static func parseDate(_ text: String, formats: [String], formatter: DateFormatter) -> Date? {\n        for pattern in formats {\n            formatter.dateFormat = pattern\n            if let date = formatter.date(from: text) { return date }\n        }\n        return nil\n    }\n\n    /// Extract login/plan string from CLI output.\n    private static func extractLoginMethod(text: String) -> String? {\n        guard !text.isEmpty else { return nil }\n        if let explicit = self.extractFirst(pattern: #\"(?i)login\\s+method:\\s*(.+)\"#, text: text) {\n            return ClaudePlan.cliCompatibilityLoginMethod(self.cleanPlan(explicit))\n        }\n        // Capture any \"Claude <...>\" phrase (e.g., Max/Pro/Ultra/Team) to avoid future plan-name churn.\n        // Strip any leading ANSI that may have survived (rare) before matching.\n        let planPattern = #\"(?i)(claude\\s+[a-z0-9][a-z0-9\\s._-]{0,24})\"#\n        var candidates: [String] = []\n        if let regex = try? NSRegularExpression(pattern: planPattern, options: []) {\n            let nsrange = NSRange(text.startIndex..<text.endIndex, in: text)\n            regex.enumerateMatches(in: text, options: [], range: nsrange) { match, _, _ in\n                guard let match,\n                      match.numberOfRanges >= 2,\n                      let r = Range(match.range(at: 1), in: text) else { return }\n                let raw = String(text[r])\n                let val = ClaudePlan.cliCompatibilityLoginMethod(Self.cleanPlan(raw)) ?? Self.cleanPlan(raw)\n                candidates.append(val)\n            }\n        }\n        if let plan = candidates.first(where: { cand in\n            let lower = cand.lowercased()\n            return !lower.contains(\"code v\") && !lower.contains(\"code version\") && !lower.contains(\"code\")\n        }) {\n            return plan\n        }\n        return nil\n    }\n\n    /// Strips ANSI and stray bracketed codes like \"[22m\" that can survive CLI output.\n    private static func cleanPlan(_ text: String) -> String {\n        UsageFormatter.cleanPlanName(text)\n    }\n\n    private static func dumpIfNeeded(enabled: Bool, reason: String, usage: String, status: String?) {\n        guard enabled else { return }\n        let stamp = ISO8601DateFormatter().string(from: Date())\n        var parts = [\n            \"=== Claude parse dump @ \\(stamp) ===\",\n            \"Reason: \\(reason)\",\n            \"\",\n            \"--- usage (clean) ---\",\n            usage,\n            \"\",\n        ]\n        if let status {\n            parts.append(contentsOf: [\n                \"--- status (raw/optional) ---\",\n                status,\n                \"\",\n            ])\n        }\n        let body = parts.joined(separator: \"\\n\")\n        Task { @MainActor in self.recordDump(body) }\n    }\n\n    // MARK: - Dump storage (in-memory ring buffer)\n\n    @MainActor private static var recentDumps: [String] = []\n\n    @MainActor private static func recordDump(_ text: String) {\n        if self.recentDumps.count >= 5 { self.recentDumps.removeFirst() }\n        self.recentDumps.append(text)\n    }\n\n    public static func latestDumps() async -> String {\n        await MainActor.run {\n            let result = Self.recentDumps.joined(separator: \"\\n\\n---\\n\\n\")\n            return result.isEmpty ? \"No Claude parse dumps captured yet.\" : result\n        }\n    }\n\n    #if DEBUG\n    public static func _replaceDumpsForTesting(_ dumps: [String]) async {\n        await MainActor.run {\n            self.recentDumps = dumps\n        }\n    }\n    #endif\n\n    private static func extractUsageErrorJSON(text: String) -> String? {\n        let pattern = #\"Failed\\s*to\\s*load\\s*usage\\s*data:\\s*(\\{.*\\})\"#\n        guard let regex = try? NSRegularExpression(pattern: pattern, options: [.dotMatchesLineSeparators]) else {\n            return nil\n        }\n        let range = NSRange(text.startIndex..<text.endIndex, in: text)\n        guard let match = regex.firstMatch(in: text, options: [], range: range),\n              match.numberOfRanges >= 2,\n              let jsonRange = Range(match.range(at: 1), in: text)\n        else {\n            return nil\n        }\n\n        let jsonString = String(text[jsonRange])\n        let compactJSON = jsonString.replacingOccurrences(of: \"\\r\", with: \"\").replacingOccurrences(of: \"\\n\", with: \"\")\n        let data = (compactJSON.isEmpty ? jsonString : compactJSON).data(using: .utf8)\n        guard let data,\n              let payload = try? JSONSerialization.jsonObject(with: data) as? [String: Any],\n              let error = payload[\"error\"] as? [String: Any]\n        else {\n            return nil\n        }\n\n        let message = (error[\"message\"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines)\n        let details = error[\"details\"] as? [String: Any]\n        let code = (details?[\"error_code\"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines)\n        let type = (error[\"type\"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()\n\n        if type == \"rate_limit_error\" {\n            return \"Claude CLI usage endpoint is rate limited right now. Please try again later.\"\n        }\n\n        var parts: [String] = []\n        if let message, !message.isEmpty { parts.append(message) }\n        if let code, !code.isEmpty { parts.append(\"(\\(code))\") }\n\n        guard !parts.isEmpty else { return nil }\n        let hint = parts.joined(separator: \" \")\n\n        if let code, code.lowercased().contains(\"token\") {\n            return \"\\(hint). Run `claude login` to refresh.\"\n        }\n        return \"Claude CLI error: \\(hint)\"\n    }\n\n    // MARK: - Process helpers\n\n    private static func resolvedBinaryPath(\n        binaryName: String,\n        environment: [String: String] = ProcessInfo.processInfo.environment) -> String?\n    {\n        if binaryName.contains(\"/\") {\n            return binaryName\n        }\n        return ClaudeCLIResolver.resolvedBinaryPath(environment: environment)\n    }\n\n    private static func isBinaryAvailable(_ binaryPathOrName: String?) -> Bool {\n        guard let binaryPathOrName else { return false }\n        return FileManager.default.isExecutableFile(atPath: binaryPathOrName)\n            || TTYCommandRunner.which(binaryPathOrName) != nil\n    }\n\n    static func probeWorkingDirectoryURL() -> URL {\n        let fm = FileManager.default\n        let base = fm.urls(for: .applicationSupportDirectory, in: .userDomainMask).first ?? fm.temporaryDirectory\n        let dir = base\n            .appendingPathComponent(\"CodexBar\", isDirectory: true)\n            .appendingPathComponent(\"ClaudeProbe\", isDirectory: true)\n        do {\n            try fm.createDirectory(at: dir, withIntermediateDirectories: true)\n            return dir\n        } catch {\n            return fm.temporaryDirectory\n        }\n    }\n\n    /// Run claude CLI inside a PTY so we can respond to interactive permission prompts.\n    private static func capture(subcommand: String, binary: String, timeout: TimeInterval) async throws -> String {\n        let stopOnSubstrings = subcommand == \"/usage\"\n            ? [\n                \"Current week (all models)\",\n                \"Current week (Opus)\",\n                \"Current week (Sonnet only)\",\n                \"Current week (Sonnet)\",\n                \"Current session\",\n                \"Failed to load usage data\",\n                \"failed to load usage data\",\n                \"Failedto loadusagedata\",\n                \"failedtoloadusagedata\",\n            ]\n            : []\n        let idleTimeout: TimeInterval? = subcommand == \"/usage\" ? nil : 3.0\n        let sendEnterEvery: TimeInterval? = subcommand == \"/usage\" ? 0.8 : nil\n        do {\n            return try await ClaudeCLISession.shared.capture(\n                subcommand: subcommand,\n                binary: binary,\n                timeout: timeout,\n                idleTimeout: idleTimeout,\n                stopOnSubstrings: stopOnSubstrings,\n                settleAfterStop: subcommand == \"/usage\" ? 2.0 : 0.25,\n                sendEnterEvery: sendEnterEvery)\n        } catch ClaudeCLISession.SessionError.processExited {\n            await ClaudeCLISession.shared.reset()\n            throw ClaudeStatusProbeError.timedOut\n        } catch ClaudeCLISession.SessionError.timedOut {\n            throw ClaudeStatusProbeError.timedOut\n        } catch ClaudeCLISession.SessionError.launchFailed(_) {\n            throw ClaudeStatusProbeError.claudeNotInstalled\n        } catch {\n            await ClaudeCLISession.shared.reset()\n            throw error\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Claude/ClaudeUsageDataSource.swift",
    "content": "import Foundation\n\npublic enum ClaudeUsageDataSource: String, CaseIterable, Identifiable, Sendable {\n    case auto\n    case oauth\n    case web\n    case cli\n\n    public var id: String {\n        self.rawValue\n    }\n\n    public var displayName: String {\n        switch self {\n        case .auto: \"Auto\"\n        case .oauth: \"OAuth API\"\n        case .web: \"Web API (cookies)\"\n        case .cli: \"CLI (PTY)\"\n        }\n    }\n\n    public var sourceLabel: String {\n        switch self {\n        case .auto:\n            \"auto\"\n        case .oauth:\n            \"oauth\"\n        case .web:\n            \"web\"\n        case .cli:\n            \"cli\"\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Claude/ClaudeUsageFetcher.swift",
    "content": "import Foundation\n\npublic protocol ClaudeUsageFetching: Sendable {\n    func loadLatestUsage(model: String) async throws -> ClaudeUsageSnapshot\n    func debugRawProbe(model: String) async -> String\n    func detectVersion() -> String?\n}\n\npublic struct ClaudeUsageSnapshot: Sendable {\n    public let primary: RateWindow\n    public let secondary: RateWindow?\n    public let opus: RateWindow?\n    public let providerCost: ProviderCostSnapshot?\n    public let updatedAt: Date\n    public let accountEmail: String?\n    public let accountOrganization: String?\n    public let loginMethod: String?\n    public let rawText: String?\n\n    public init(\n        primary: RateWindow,\n        secondary: RateWindow?,\n        opus: RateWindow?,\n        providerCost: ProviderCostSnapshot? = nil,\n        updatedAt: Date,\n        accountEmail: String?,\n        accountOrganization: String?,\n        loginMethod: String?,\n        rawText: String?)\n    {\n        self.primary = primary\n        self.secondary = secondary\n        self.opus = opus\n        self.providerCost = providerCost\n        self.updatedAt = updatedAt\n        self.accountEmail = accountEmail\n        self.accountOrganization = accountOrganization\n        self.loginMethod = loginMethod\n        self.rawText = rawText\n    }\n}\n\npublic enum ClaudeUsageError: LocalizedError, Sendable {\n    case claudeNotInstalled\n    case parseFailed(String)\n    case oauthFailed(String)\n\n    public var errorDescription: String? {\n        switch self {\n        case .claudeNotInstalled:\n            \"Claude CLI is not installed. Install it from https://code.claude.com/docs/en/overview.\"\n        case let .parseFailed(details):\n            \"Could not parse Claude usage: \\(details)\"\n        case let .oauthFailed(details):\n            details\n        }\n    }\n}\n\npublic struct ClaudeUsageFetcher: ClaudeUsageFetching, Sendable {\n    private struct Configuration: Sendable {\n        let environment: [String: String]\n        let runtime: ProviderRuntime\n        let dataSource: ClaudeUsageDataSource\n        let oauthKeychainPromptCooldownEnabled: Bool\n        let allowBackgroundDelegatedRefresh: Bool\n        let allowStartupBootstrapPrompt: Bool\n        let useWebExtras: Bool\n        let manualCookieHeader: String?\n        let keepCLISessionsAlive: Bool\n        let browserDetection: BrowserDetection\n    }\n\n    private let configuration: Configuration\n    private static let log = CodexBarLog.logger(LogCategories.claudeUsage)\n    private static var isClaudeOAuthFlowDebugEnabled: Bool {\n        ProcessInfo.processInfo.environment[\"CODEXBAR_DEBUG_CLAUDE_OAUTH_FLOW\"] == \"1\"\n    }\n\n    private var environment: [String: String] {\n        self.configuration.environment\n    }\n\n    private var runtime: ProviderRuntime {\n        self.configuration.runtime\n    }\n\n    private var dataSource: ClaudeUsageDataSource {\n        self.configuration.dataSource\n    }\n\n    private var oauthKeychainPromptCooldownEnabled: Bool {\n        self.configuration.oauthKeychainPromptCooldownEnabled\n    }\n\n    private var allowBackgroundDelegatedRefresh: Bool {\n        self.configuration.allowBackgroundDelegatedRefresh\n    }\n\n    private var allowStartupBootstrapPrompt: Bool {\n        self.configuration.allowStartupBootstrapPrompt\n    }\n\n    private var useWebExtras: Bool {\n        self.configuration.useWebExtras\n    }\n\n    private var manualCookieHeader: String? {\n        self.configuration.manualCookieHeader\n    }\n\n    private var keepCLISessionsAlive: Bool {\n        self.configuration.keepCLISessionsAlive\n    }\n\n    private var browserDetection: BrowserDetection {\n        self.configuration.browserDetection\n    }\n\n    private struct ClaudeOAuthKeychainPromptPolicy: Sendable {\n        let mode: ClaudeOAuthKeychainPromptMode\n        let isApplicable: Bool\n        let interaction: ProviderInteraction\n\n        var canPromptNow: Bool {\n            switch self.mode {\n            case .never:\n                false\n            case .onlyOnUserAction:\n                self.interaction == .userInitiated\n            case .always:\n                true\n            }\n        }\n\n        /// Respect the Keychain prompt cooldown for background operations to avoid spamming system dialogs.\n        /// User actions (menu open / refresh / settings) are allowed to bypass the cooldown.\n        var shouldRespectKeychainPromptCooldown: Bool {\n            self.interaction != .userInitiated\n        }\n\n        var interactionLabel: String {\n            self.interaction == .userInitiated ? \"user\" : \"background\"\n        }\n    }\n\n    private static func currentClaudeOAuthKeychainPromptPolicy() -> ClaudeOAuthKeychainPromptPolicy {\n        let isApplicable = ClaudeOAuthKeychainPromptPreference.isApplicable()\n        let policy = ClaudeOAuthKeychainPromptPolicy(\n            mode: ClaudeOAuthKeychainPromptPreference.current(),\n            isApplicable: isApplicable,\n            interaction: ProviderInteractionContext.current)\n\n        // User actions should be able to immediately retry a repair after a background cooldown was recorded.\n        if policy.isApplicable, policy.interaction == .userInitiated {\n            if ClaudeOAuthKeychainAccessGate.clearDenied() {\n                Self.log.info(\"Claude OAuth keychain cooldown cleared by user action\")\n            }\n        }\n        return policy\n    }\n\n    private static func assertDelegatedRefreshAllowedInCurrentInteraction(\n        policy: ClaudeOAuthKeychainPromptPolicy,\n        allowBackgroundDelegatedRefresh: Bool) throws\n    {\n        guard policy.isApplicable else { return }\n        if policy.mode == .never {\n            throw ClaudeUsageError.oauthFailed(\"Delegated refresh is disabled by 'never' keychain policy.\")\n        }\n        if policy.mode == .onlyOnUserAction,\n           policy.interaction != .userInitiated,\n           !allowBackgroundDelegatedRefresh\n        {\n            throw ClaudeUsageError.oauthFailed(\n                \"Claude OAuth token expired, but background repair is suppressed when Keychain prompt policy \"\n                    + \"is set to only prompt on user action. Open the CodexBar menu or click Refresh to retry.\")\n        }\n    }\n\n    #if DEBUG\n    @TaskLocal static var loadOAuthCredentialsOverride: (@Sendable (\n        [String: String],\n        Bool,\n        Bool) async throws -> ClaudeOAuthCredentials)?\n    @TaskLocal static var fetchOAuthUsageOverride: (@Sendable (String) async throws -> OAuthUsageResponse)?\n    @TaskLocal static var delegatedRefreshAttemptOverride: (@Sendable (\n        Date,\n        TimeInterval,\n        [String: String]) async -> ClaudeOAuthDelegatedRefreshCoordinator.Outcome)?\n    @TaskLocal static var hasCachedCredentialsOverride: Bool?\n    #endif\n\n    /// Creates a new ClaudeUsageFetcher.\n    /// - Parameters:\n    ///   - environment: Process environment (default: current process environment)\n    ///   - dataSource: Usage data source (default: OAuth API).\n    ///   - useWebExtras: If true, attempts to enrich usage with Claude web data (cookies).\n    public init(\n        browserDetection: BrowserDetection,\n        environment: [String: String] = ProcessInfo.processInfo.environment,\n        runtime: ProviderRuntime = .app,\n        dataSource: ClaudeUsageDataSource = .oauth,\n        oauthKeychainPromptCooldownEnabled: Bool = false,\n        allowBackgroundDelegatedRefresh: Bool = false,\n        allowStartupBootstrapPrompt: Bool = false,\n        useWebExtras: Bool = false,\n        manualCookieHeader: String? = nil,\n        keepCLISessionsAlive: Bool = false)\n    {\n        self.configuration = Configuration(\n            environment: environment,\n            runtime: runtime,\n            dataSource: dataSource,\n            oauthKeychainPromptCooldownEnabled: oauthKeychainPromptCooldownEnabled,\n            allowBackgroundDelegatedRefresh: allowBackgroundDelegatedRefresh,\n            allowStartupBootstrapPrompt: allowStartupBootstrapPrompt,\n            useWebExtras: useWebExtras,\n            manualCookieHeader: manualCookieHeader,\n            keepCLISessionsAlive: keepCLISessionsAlive,\n            browserDetection: browserDetection)\n    }\n\n    private struct OAuthExecutor: Sendable {\n        let fetcher: ClaudeUsageFetcher\n\n        func load(allowDelegatedRetry: Bool) async throws -> ClaudeUsageSnapshot {\n            do {\n                let promptPolicy = ClaudeUsageFetcher.currentClaudeOAuthKeychainPromptPolicy()\n\n                #if DEBUG\n                let hasCache = ClaudeUsageFetcher.hasCachedCredentialsOverride\n                    ?? ClaudeOAuthCredentialsStore.hasCachedCredentials(environment: self.fetcher.environment)\n                #else\n                let hasCache = ClaudeOAuthCredentialsStore.hasCachedCredentials(environment: self.fetcher.environment)\n                #endif\n\n                let startupBootstrapOverride = self.shouldAllowStartupBootstrapPrompt(\n                    policy: promptPolicy,\n                    hasCache: hasCache)\n                let allowKeychainPrompt = (promptPolicy.canPromptNow || startupBootstrapOverride) && !hasCache\n                ClaudeUsageFetcher.logOAuthBootstrapPromptDecision(\n                    allowKeychainPrompt: allowKeychainPrompt,\n                    policy: promptPolicy,\n                    hasCache: hasCache,\n                    startupBootstrapOverride: startupBootstrapOverride)\n\n                let credentials = try await ClaudeOAuthCredentialsStore.$allowBackgroundPromptBootstrap\n                    .withValue(startupBootstrapOverride) {\n                        try await ClaudeUsageFetcher.loadOAuthCredentials(\n                            environment: self.fetcher.environment,\n                            allowKeychainPrompt: allowKeychainPrompt,\n                            respectKeychainPromptCooldown: promptPolicy.shouldRespectKeychainPromptCooldown)\n                    }\n\n                try self.validateRequiredOAuthScope(credentials)\n                let usage = try await ClaudeUsageFetcher.fetchOAuthUsage(accessToken: credentials.accessToken)\n                return try ClaudeUsageFetcher.mapOAuthUsage(usage, credentials: credentials)\n            } catch let error as CancellationError {\n                throw error\n            } catch let error as ClaudeUsageError {\n                throw error\n            } catch let error as ClaudeOAuthCredentialsError {\n                if case .refreshDelegatedToClaudeCLI = error {\n                    return try await self.loadAfterDelegatedRefresh(allowDelegatedRetry: allowDelegatedRetry)\n                }\n                throw ClaudeUsageError.oauthFailed(error.localizedDescription)\n            } catch let error as ClaudeOAuthFetchError {\n                ClaudeOAuthCredentialsStore.invalidateCache()\n                if case let .serverError(statusCode, body) = error,\n                   statusCode == 403,\n                   body?.contains(\"user:profile\") ?? false\n                {\n                    throw ClaudeUsageError.oauthFailed(\n                        \"Claude OAuth token does not meet scope requirement 'user:profile'. \"\n                            + \"Run `claude setup-token` to re-generate credentials, or switch Claude Source to \"\n                            + \"Web/CLI.\")\n                }\n                throw ClaudeUsageError.oauthFailed(error.localizedDescription)\n            } catch {\n                throw ClaudeUsageError.oauthFailed(error.localizedDescription)\n            }\n        }\n\n        private func shouldAllowStartupBootstrapPrompt(\n            policy: ClaudeOAuthKeychainPromptPolicy,\n            hasCache: Bool) -> Bool\n        {\n            guard policy.isApplicable else { return false }\n            guard self.fetcher.allowStartupBootstrapPrompt else { return false }\n            guard !hasCache else { return false }\n            guard policy.mode == .onlyOnUserAction else { return false }\n            guard policy.interaction == .background else { return false }\n            return ProviderRefreshContext.current == .startup\n        }\n\n        private func loadAfterDelegatedRefresh(allowDelegatedRetry: Bool) async throws -> ClaudeUsageSnapshot {\n            guard allowDelegatedRetry else {\n                throw ClaudeUsageError.oauthFailed(\n                    \"Claude OAuth token expired and delegated Claude CLI refresh did not recover. \"\n                        + \"Run `claude login`, then retry.\")\n            }\n\n            try Task.checkCancellation()\n\n            let delegatedPromptPolicy = ClaudeUsageFetcher.currentClaudeOAuthKeychainPromptPolicy()\n            try ClaudeUsageFetcher.assertDelegatedRefreshAllowedInCurrentInteraction(\n                policy: delegatedPromptPolicy,\n                allowBackgroundDelegatedRefresh: self.fetcher.allowBackgroundDelegatedRefresh)\n\n            let delegatedOutcome = await ClaudeUsageFetcher.attemptDelegatedRefresh(\n                environment: self.fetcher.environment)\n            ClaudeUsageFetcher.log.info(\n                \"Claude OAuth delegated refresh attempted\",\n                metadata: [\n                    \"outcome\": ClaudeUsageFetcher.delegatedRefreshOutcomeLabel(delegatedOutcome),\n                ])\n\n            do {\n                if self.fetcher.oauthKeychainPromptCooldownEnabled {\n                    switch delegatedOutcome {\n                    case .skippedByCooldown, .cliUnavailable:\n                        throw ClaudeUsageError.oauthFailed(\n                            \"Claude OAuth token expired; delegated refresh is unavailable (outcome=\"\n                                + \"\\(ClaudeUsageFetcher.delegatedRefreshOutcomeLabel(delegatedOutcome))).\")\n                    case .attemptedSucceeded, .attemptedFailed:\n                        break\n                    }\n                }\n\n                try Task.checkCancellation()\n\n                _ = ClaudeOAuthCredentialsStore.invalidateCacheIfCredentialsFileChanged()\n\n                let didSyncSilently = delegatedOutcome == .attemptedSucceeded\n                    && ClaudeOAuthCredentialsStore.syncFromClaudeKeychainWithoutPrompt(now: Date())\n\n                let promptPolicy = ClaudeUsageFetcher.currentClaudeOAuthKeychainPromptPolicy()\n                ClaudeUsageFetcher.logDeferredBackgroundDelegatedRecoveryIfNeeded(\n                    delegatedOutcome: delegatedOutcome,\n                    didSyncSilently: didSyncSilently,\n                    policy: promptPolicy)\n                let retryAllowKeychainPrompt = promptPolicy.canPromptNow && !didSyncSilently\n                if retryAllowKeychainPrompt {\n                    ClaudeUsageFetcher.log.info(\n                        \"Claude OAuth keychain prompt allowed (post-delegation retry)\",\n                        metadata: [\n                            \"interaction\": promptPolicy.interactionLabel,\n                            \"promptMode\": promptPolicy.mode.rawValue,\n                            \"promptPolicyApplicable\": \"\\(promptPolicy.isApplicable)\",\n                            \"delegatedOutcome\": ClaudeUsageFetcher.delegatedRefreshOutcomeLabel(delegatedOutcome),\n                            \"didSyncSilently\": \"\\(didSyncSilently)\",\n                        ])\n                }\n                if ClaudeUsageFetcher.isClaudeOAuthFlowDebugEnabled {\n                    ClaudeUsageFetcher.log.debug(\n                        \"Claude OAuth credential load (post-delegation retry start)\",\n                        metadata: [\n                            \"cooldownEnabled\": \"\\(self.fetcher.oauthKeychainPromptCooldownEnabled)\",\n                            \"didSyncSilently\": \"\\(didSyncSilently)\",\n                            \"allowKeychainPrompt\": \"\\(retryAllowKeychainPrompt)\",\n                            \"delegatedOutcome\": ClaudeUsageFetcher.delegatedRefreshOutcomeLabel(delegatedOutcome),\n                            \"interaction\": promptPolicy.interactionLabel,\n                            \"promptMode\": promptPolicy.mode.rawValue,\n                            \"promptPolicyApplicable\": \"\\(promptPolicy.isApplicable)\",\n                        ])\n                }\n\n                let refreshedCredentials = try await ClaudeUsageFetcher.loadOAuthCredentials(\n                    environment: self.fetcher.environment,\n                    allowKeychainPrompt: retryAllowKeychainPrompt,\n                    respectKeychainPromptCooldown: promptPolicy.shouldRespectKeychainPromptCooldown)\n                if ClaudeUsageFetcher.isClaudeOAuthFlowDebugEnabled {\n                    ClaudeUsageFetcher.log.debug(\n                        \"Claude OAuth credential load (post-delegation retry)\",\n                        metadata: [\n                            \"cooldownEnabled\": \"\\(self.fetcher.oauthKeychainPromptCooldownEnabled)\",\n                            \"didSyncSilently\": \"\\(didSyncSilently)\",\n                            \"allowKeychainPrompt\": \"\\(retryAllowKeychainPrompt)\",\n                            \"delegatedOutcome\": ClaudeUsageFetcher.delegatedRefreshOutcomeLabel(delegatedOutcome),\n                            \"interaction\": promptPolicy.interactionLabel,\n                            \"promptMode\": promptPolicy.mode.rawValue,\n                            \"promptPolicyApplicable\": \"\\(promptPolicy.isApplicable)\",\n                        ])\n                }\n\n                try self.validateRequiredOAuthScope(refreshedCredentials)\n                let usage = try await ClaudeUsageFetcher.fetchOAuthUsage(\n                    accessToken: refreshedCredentials.accessToken)\n                return try ClaudeUsageFetcher.mapOAuthUsage(usage, credentials: refreshedCredentials)\n            } catch {\n                ClaudeUsageFetcher.log.debug(\n                    \"Claude OAuth post-delegation retry failed\",\n                    metadata: ClaudeUsageFetcher.delegatedRetryFailureMetadata(\n                        error: error,\n                        oauthKeychainPromptCooldownEnabled: self.fetcher.oauthKeychainPromptCooldownEnabled,\n                        delegatedOutcome: delegatedOutcome))\n                throw ClaudeUsageError.oauthFailed(\n                    ClaudeUsageFetcher.delegatedRefreshFailureMessage(\n                        for: delegatedOutcome,\n                        retryError: error))\n            }\n        }\n\n        private func validateRequiredOAuthScope(_ credentials: ClaudeOAuthCredentials) throws {\n            guard credentials.scopes.contains(\"user:profile\") else {\n                let scopes = credentials.scopes.joined(separator: \", \")\n                let detail = scopes.isEmpty\n                    ? \"Claude OAuth token missing 'user:profile' scope.\"\n                    : \"Claude OAuth token missing 'user:profile' scope (has: \\(scopes)).\"\n                throw ClaudeUsageError.oauthFailed(\n                    detail + \" Run `claude setup-token` to re-generate credentials, or switch Claude Source to \"\n                        + \"Web/CLI.\")\n            }\n        }\n    }\n\n    private struct StepExecutor: Sendable {\n        let fetcher: ClaudeUsageFetcher\n\n        func loadLatestUsage(model: String) async throws -> ClaudeUsageSnapshot {\n            switch self.fetcher.dataSource {\n            case .auto:\n                return try await self.executeAuto(model: model)\n            case .oauth:\n                var snapshot = try await self.fetcher.loadViaOAuth(allowDelegatedRetry: true)\n                snapshot = await self.fetcher.applyWebExtrasIfNeeded(to: snapshot)\n                return snapshot\n            case .web:\n                return try await self.fetcher.loadViaWebAPI()\n            case .cli:\n                do {\n                    var snapshot = try await self.fetcher.loadViaPTY(model: model, timeout: 10)\n                    snapshot = await self.fetcher.applyWebExtrasIfNeeded(to: snapshot)\n                    return snapshot\n                } catch {\n                    var snapshot = try await self.fetcher.loadViaPTY(model: model, timeout: 24)\n                    snapshot = await self.fetcher.applyWebExtrasIfNeeded(to: snapshot)\n                    return snapshot\n                }\n            }\n        }\n\n        private func executeAuto(model: String) async throws -> ClaudeUsageSnapshot {\n            let plan = await self.makeAutoFetchPlan()\n            self.logAutoPlan(plan)\n\n            let executionSteps = plan.executionSteps\n            for (index, step) in executionSteps.enumerated() {\n                do {\n                    return try await self.execute(step: step, model: model)\n                } catch {\n                    if index < executionSteps.count - 1 {\n                        ClaudeUsageFetcher.log.debug(\n                            \"Claude planner step failed; falling back to next step\",\n                            metadata: [\n                                \"step\": step.dataSource.rawValue,\n                                \"reason\": step.inclusionReason.rawValue,\n                                \"errorType\": String(describing: type(of: error)),\n                            ])\n                        continue\n                    }\n                    throw error\n                }\n            }\n            throw ClaudeUsageError.parseFailed(\"Claude planner produced no executable steps.\")\n        }\n\n        private func makeAutoFetchPlan() async -> ClaudeFetchPlan {\n            let hasWebSession =\n                if let header = self.fetcher.manualCookieHeader {\n                    ClaudeWebAPIFetcher.hasSessionKey(cookieHeader: header)\n                } else {\n                    ClaudeWebAPIFetcher.hasSessionKey(browserDetection: self.fetcher.browserDetection)\n                }\n            let hasCLI = ClaudeCLIResolver.isAvailable(environment: self.fetcher.environment)\n            return ClaudeSourcePlanner.resolve(input: ClaudeSourcePlanningInput(\n                runtime: self.fetcher.runtime,\n                selectedDataSource: .auto,\n                webExtrasEnabled: self.fetcher.useWebExtras,\n                hasWebSession: hasWebSession,\n                hasCLI: hasCLI,\n                hasOAuthCredentials: ClaudeOAuthPlanningAvailability.isAvailable(\n                    runtime: self.fetcher.runtime,\n                    sourceMode: .auto,\n                    environment: self.fetcher.environment)))\n        }\n\n        private func logAutoPlan(_ plan: ClaudeFetchPlan) {\n            var metadata: [String: String] = [\n                \"plannerOrder\": plan.orderLabel,\n                \"selected\": plan.preferredStep?.dataSource.rawValue ?? \"none\",\n                \"noSourceAvailable\": \"\\(plan.isNoSourceAvailable)\",\n                \"webExtrasEnabled\": \"\\(self.fetcher.useWebExtras)\",\n                \"oauthReadStrategy\": ClaudeOAuthKeychainReadStrategyPreference.current().rawValue,\n            ]\n            for (index, step) in plan.orderedSteps.enumerated() {\n                metadata[\"step\\(index)\"] =\n                    \"\\(step.dataSource.rawValue):\\(step.inclusionReason.rawValue):\\(step.isPlausiblyAvailable)\"\n            }\n            ClaudeUsageFetcher.log.debug(\"Claude auto source planner\", metadata: metadata)\n        }\n\n        private func execute(step: ClaudeFetchPlanStep, model: String) async throws -> ClaudeUsageSnapshot {\n            switch step.dataSource {\n            case .oauth:\n                var snapshot = try await self.fetcher.loadViaOAuth(allowDelegatedRetry: true)\n                snapshot = await self.fetcher.applyWebExtrasIfNeeded(to: snapshot)\n                return snapshot\n            case .web:\n                return try await self.fetcher.loadViaWebAPI()\n            case .cli:\n                var snapshot = try await self.fetcher.loadViaPTY(model: model, timeout: 10)\n                snapshot = await self.fetcher.applyWebExtrasIfNeeded(to: snapshot)\n                return snapshot\n            case .auto:\n                throw ClaudeUsageError.parseFailed(\"Planner emitted invalid auto execution step.\")\n            }\n        }\n    }\n}\n\nextension ClaudeUsageFetcher {\n    // MARK: - Parsing helpers\n\n    public static func parse(json: Data) -> ClaudeUsageSnapshot? {\n        guard let output = String(data: json, encoding: .utf8) else { return nil }\n        return try? Self.parse(output: output)\n    }\n\n    private static func parse(output: String) throws -> ClaudeUsageSnapshot {\n        guard\n            let data = output.data(using: .utf8),\n            let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any]\n        else {\n            throw ClaudeUsageError.parseFailed(output.prefix(500).description)\n        }\n\n        if let ok = obj[\"ok\"] as? Bool, !ok {\n            let hint = obj[\"hint\"] as? String ?? (obj[\"pane_preview\"] as? String ?? \"\")\n            throw ClaudeUsageError.parseFailed(hint)\n        }\n\n        func firstWindowDict(_ keys: [String]) -> [String: Any]? {\n            for key in keys {\n                if let dict = obj[key] as? [String: Any] { return dict }\n            }\n            return nil\n        }\n\n        func makeWindow(_ dict: [String: Any]?) -> RateWindow? {\n            guard let dict else { return nil }\n            let pct = (dict[\"pct_used\"] as? NSNumber)?.doubleValue ?? 0\n            let resetText = dict[\"resets\"] as? String\n            return RateWindow(\n                usedPercent: pct,\n                windowMinutes: nil,\n                resetsAt: Self.parseReset(text: resetText),\n                resetDescription: resetText)\n        }\n\n        guard let session = makeWindow(firstWindowDict([\"session_5h\"])) else {\n            throw ClaudeUsageError.parseFailed(\"missing session data\")\n        }\n        let weekAll = makeWindow(firstWindowDict([\"week_all_models\", \"week_all\"]))\n\n        let rawEmail = (obj[\"account_email\"] as? String)?.trimmingCharacters(\n            in: .whitespacesAndNewlines)\n        let email = (rawEmail?.isEmpty ?? true) ? nil : rawEmail\n        let rawOrg = (obj[\"account_org\"] as? String)?.trimmingCharacters(\n            in: .whitespacesAndNewlines)\n        let org = (rawOrg?.isEmpty ?? true) ? nil : rawOrg\n        let loginMethod = (obj[\"login_method\"] as? String)?.trimmingCharacters(\n            in: .whitespacesAndNewlines)\n        let opusWindow: RateWindow? = {\n            let candidates = firstWindowDict([\n                \"week_sonnet\",\n                \"week_sonnet_only\",\n                \"week_opus\",\n            ])\n            guard let opus = candidates else { return nil }\n            let pct = (opus[\"pct_used\"] as? NSNumber)?.doubleValue ?? 0\n            let resets = opus[\"resets\"] as? String\n            return RateWindow(\n                usedPercent: pct,\n                windowMinutes: nil,\n                resetsAt: Self.parseReset(text: resets),\n                resetDescription: resets)\n        }()\n        return ClaudeUsageSnapshot(\n            primary: session,\n            secondary: weekAll,\n            opus: opusWindow,\n            providerCost: nil,\n            updatedAt: Date(),\n            accountEmail: email,\n            accountOrganization: org,\n            loginMethod: loginMethod,\n            rawText: output)\n    }\n\n    private static func parseReset(text: String?) -> Date? {\n        guard let text, !text.isEmpty else { return nil }\n        let parts = text.split(separator: \"(\")\n        let timePart = parts.first?.trimmingCharacters(in: .whitespaces)\n        let tzPart =\n            parts.count > 1\n                ? parts[1].replacingOccurrences(of: \")\", with: \"\").trimmingCharacters(in: .whitespaces)\n                : nil\n        let tz = tzPart.flatMap(TimeZone.init(identifier:))\n        let formats = [\"ha\", \"h:mma\", \"MMM d 'at' ha\", \"MMM d 'at' h:mma\"]\n        for format in formats {\n            let df = DateFormatter()\n            df.locale = Locale(identifier: \"en_US_POSIX\")\n            df.timeZone = tz ?? TimeZone.current\n            df.dateFormat = format\n            if let t = timePart, let date = df.date(from: t) { return date }\n        }\n        return nil\n    }\n\n    // MARK: - Public API\n\n    public func detectVersion() -> String? {\n        ProviderVersionDetector.claudeVersion()\n    }\n\n    public func debugRawProbe(model: String = \"sonnet\") async -> String {\n        do {\n            let snap = try await self.loadViaPTY(model: model, timeout: 10)\n            let opus = snap.opus?.remainingPercent ?? -1\n            let email = snap.accountEmail ?? \"nil\"\n            let org = snap.accountOrganization ?? \"nil\"\n            let weekly = snap.secondary?.remainingPercent ?? -1\n            let primary = snap.primary.remainingPercent\n            return \"\"\"\n            session_left=\\(primary) weekly_left=\\(weekly)\n            opus_left=\\(opus) email \\(email) org \\(org)\n            \\(snap)\n            \"\"\"\n        } catch {\n            return \"Probe failed: \\(error)\"\n        }\n    }\n\n    public func loadLatestUsage(model: String = \"sonnet\") async throws -> ClaudeUsageSnapshot {\n        try await StepExecutor(fetcher: self).loadLatestUsage(model: model)\n    }\n}\n\nextension ClaudeUsageFetcher {\n    // MARK: - OAuth API path\n\n    private static func logOAuthBootstrapPromptDecision(\n        allowKeychainPrompt: Bool,\n        policy: ClaudeOAuthKeychainPromptPolicy,\n        hasCache: Bool,\n        startupBootstrapOverride: Bool)\n    {\n        guard allowKeychainPrompt else { return }\n        self.log.info(\n            \"Claude OAuth keychain prompt allowed (bootstrap)\",\n            metadata: [\n                \"interaction\": policy.interactionLabel,\n                \"promptMode\": policy.mode.rawValue,\n                \"promptPolicyApplicable\": \"\\(policy.isApplicable)\",\n                \"hasCache\": \"\\(hasCache)\",\n                \"startupBootstrapOverride\": \"\\(startupBootstrapOverride)\",\n            ])\n    }\n\n    private static func logDeferredBackgroundDelegatedRecoveryIfNeeded(\n        delegatedOutcome: ClaudeOAuthDelegatedRefreshCoordinator.Outcome,\n        didSyncSilently: Bool,\n        policy: ClaudeOAuthKeychainPromptPolicy)\n    {\n        guard delegatedOutcome == .attemptedSucceeded else { return }\n        guard !didSyncSilently else { return }\n        guard policy.mode == .onlyOnUserAction else { return }\n        guard policy.interaction == .background else { return }\n        self.log.info(\n            \"Claude OAuth delegated refresh completed; background recovery deferred until user action\",\n            metadata: [\n                \"interaction\": policy.interactionLabel,\n                \"promptMode\": policy.mode.rawValue,\n                \"delegatedOutcome\": self.delegatedRefreshOutcomeLabel(delegatedOutcome),\n            ])\n    }\n\n    private func loadViaOAuth(allowDelegatedRetry: Bool) async throws -> ClaudeUsageSnapshot {\n        try await OAuthExecutor(fetcher: self).load(allowDelegatedRetry: allowDelegatedRetry)\n    }\n\n    private static func loadOAuthCredentials(\n        environment: [String: String],\n        allowKeychainPrompt: Bool,\n        respectKeychainPromptCooldown: Bool) async throws -> ClaudeOAuthCredentials\n    {\n        #if DEBUG\n        if let override = loadOAuthCredentialsOverride {\n            return try await override(environment, allowKeychainPrompt, respectKeychainPromptCooldown)\n        }\n        #endif\n        return try await ClaudeOAuthCredentialsStore.loadWithAutoRefresh(\n            environment: environment,\n            allowKeychainPrompt: allowKeychainPrompt,\n            respectKeychainPromptCooldown: respectKeychainPromptCooldown)\n    }\n\n    private static func fetchOAuthUsage(accessToken: String) async throws -> OAuthUsageResponse {\n        #if DEBUG\n        if let override = fetchOAuthUsageOverride {\n            return try await override(accessToken)\n        }\n        #endif\n        return try await ClaudeOAuthUsageFetcher.fetchUsage(accessToken: accessToken)\n    }\n\n    private static func attemptDelegatedRefresh(\n        now: Date = Date(),\n        timeout: TimeInterval = 15,\n        environment: [String: String] = ProcessInfo.processInfo.environment)\n        async -> ClaudeOAuthDelegatedRefreshCoordinator.Outcome\n    {\n        #if DEBUG\n        if let override = delegatedRefreshAttemptOverride {\n            return await override(now, timeout, environment)\n        }\n        #endif\n        return await ClaudeOAuthDelegatedRefreshCoordinator.attempt(\n            now: now,\n            timeout: timeout,\n            environment: environment)\n    }\n\n    private static func delegatedRefreshOutcomeLabel(\n        _ outcome: ClaudeOAuthDelegatedRefreshCoordinator.Outcome) -> String\n    {\n        switch outcome {\n        case .skippedByCooldown:\n            \"skippedByCooldown\"\n        case .cliUnavailable:\n            \"cliUnavailable\"\n        case .attemptedSucceeded:\n            \"attemptedSucceeded\"\n        case .attemptedFailed:\n            \"attemptedFailed\"\n        }\n    }\n\n    private static func delegatedRefreshFailureMessage(\n        for outcome: ClaudeOAuthDelegatedRefreshCoordinator.Outcome,\n        retryError: Error) -> String\n    {\n        _ = retryError\n        switch outcome {\n        case .skippedByCooldown:\n            return \"Claude OAuth token expired and delegated refresh is cooling down. \"\n                + \"Please retry shortly, or run `claude login`.\"\n        case .cliUnavailable:\n            return \"Claude OAuth token expired and Claude CLI is not available for delegated refresh. \"\n                + \"Install/configure `claude`, or run `claude login`.\"\n        case .attemptedSucceeded:\n            return \"Claude OAuth token is still unavailable after delegated Claude CLI refresh. \"\n                + \"Run `claude login`, then retry.\"\n        case let .attemptedFailed(message):\n            return \"Claude OAuth token expired and delegated Claude CLI refresh failed: \\(message). \"\n                + \"Run `claude login`, then retry.\"\n        }\n    }\n\n    private static func delegatedRetryFailureMetadata(\n        error: Error,\n        oauthKeychainPromptCooldownEnabled: Bool,\n        delegatedOutcome: ClaudeOAuthDelegatedRefreshCoordinator.Outcome) -> [String: String]\n    {\n        var metadata: [String: String] = [\n            \"errorType\": String(describing: type(of: error)),\n            \"cooldownEnabled\": \"\\(oauthKeychainPromptCooldownEnabled)\",\n            \"delegatedOutcome\": Self.delegatedRefreshOutcomeLabel(delegatedOutcome),\n        ]\n\n        // Avoid `localizedDescription` here: some error types include server response bodies in their\n        // `errorDescription`, which can leak potentially identifying information into logs.\n        if let oauthError = error as? ClaudeOAuthFetchError {\n            switch oauthError {\n            case .unauthorized:\n                metadata[\"oauthError\"] = \"unauthorized\"\n            case .invalidResponse:\n                metadata[\"oauthError\"] = \"invalidResponse\"\n            case let .serverError(statusCode, body):\n                metadata[\"oauthError\"] = \"serverError\"\n                metadata[\"httpStatus\"] = \"\\(statusCode)\"\n                metadata[\"bodyLength\"] = \"\\(body?.utf8.count ?? 0)\"\n            case let .networkError(underlying):\n                metadata[\"oauthError\"] = \"networkError\"\n                metadata[\"underlyingType\"] = String(describing: type(of: underlying))\n            }\n        }\n\n        return metadata\n    }\n\n    private static func mapOAuthUsage(\n        _ usage: OAuthUsageResponse,\n        credentials: ClaudeOAuthCredentials) throws -> ClaudeUsageSnapshot\n    {\n        func makeWindow(_ window: OAuthUsageWindow?, windowMinutes: Int?) -> RateWindow? {\n            guard let window,\n                  let utilization = window.utilization\n            else { return nil }\n            let resetDate = ClaudeOAuthUsageFetcher.parseISO8601Date(window.resetsAt)\n            let resetDescription = resetDate.map(Self.formatResetDate)\n            return RateWindow(\n                usedPercent: utilization,\n                windowMinutes: windowMinutes,\n                resetsAt: resetDate,\n                resetDescription: resetDescription)\n        }\n\n        guard let primary = makeWindow(usage.fiveHour, windowMinutes: 5 * 60) else {\n            throw ClaudeUsageError.parseFailed(\"missing session data\")\n        }\n\n        let weekly = makeWindow(usage.sevenDay, windowMinutes: 7 * 24 * 60)\n        let modelSpecific = makeWindow(\n            usage.sevenDaySonnet ?? usage.sevenDayOpus,\n            windowMinutes: 7 * 24 * 60)\n\n        let loginMethod = ClaudePlan.oauthLoginMethod(rateLimitTier: credentials.rateLimitTier)\n        let providerCost = Self.oauthExtraUsageCost(usage.extraUsage, loginMethod: loginMethod)\n\n        return ClaudeUsageSnapshot(\n            primary: primary,\n            secondary: weekly,\n            opus: modelSpecific,\n            providerCost: providerCost,\n            updatedAt: Date(),\n            accountEmail: nil,\n            accountOrganization: nil,\n            loginMethod: loginMethod,\n            rawText: nil)\n    }\n\n    private static func oauthExtraUsageCost(\n        _ extra: OAuthExtraUsage?,\n        loginMethod: String?) -> ProviderCostSnapshot?\n    {\n        guard let extra, extra.isEnabled == true else { return nil }\n        guard let used = extra.usedCredits,\n              let limit = extra.monthlyLimit\n        else { return nil }\n        let currency = extra.currency?.trimmingCharacters(in: .whitespacesAndNewlines)\n        let code = (currency?.isEmpty ?? true) ? \"USD\" : currency!\n        let normalized = Self.normalizeClaudeExtraUsageAmounts(used: used, limit: limit)\n        return ProviderCostSnapshot(\n            used: normalized.used,\n            limit: normalized.limit,\n            currencyCode: code,\n            period: \"Monthly\",\n            resetsAt: nil,\n            updatedAt: Date())\n    }\n\n    private static func normalizeClaudeExtraUsageAmounts(\n        used: Double,\n        limit: Double) -> (used: Double, limit: Double)\n    {\n        // Claude's OAuth API returns values in cents (minor units), same as the Web API.\n        // Always convert to dollars (major units) for display consistency.\n        // See: ClaudeWebAPIFetcher.swift which always divides by 100.\n        (used: used / 100.0, limit: limit / 100.0)\n    }\n\n    // MARK: - Web API path (uses browser cookies)\n\n    private func loadViaWebAPI() async throws -> ClaudeUsageSnapshot {\n        let webData: ClaudeWebAPIFetcher.WebUsageData =\n            if let header = self.manualCookieHeader {\n                try await ClaudeWebAPIFetcher.fetchUsage(cookieHeader: header) { msg in\n                    Self.log.debug(msg)\n                }\n            } else {\n                try await ClaudeWebAPIFetcher.fetchUsage(browserDetection: self.browserDetection) { msg in\n                    Self.log.debug(msg)\n                }\n            }\n        // Convert web API data to ClaudeUsageSnapshot format\n        let primary = RateWindow(\n            usedPercent: webData.sessionPercentUsed,\n            windowMinutes: 5 * 60,\n            resetsAt: webData.sessionResetsAt,\n            resetDescription: webData.sessionResetsAt.map { Self.formatResetDate($0) })\n\n        let secondary: RateWindow? = webData.weeklyPercentUsed.map { pct in\n            RateWindow(\n                usedPercent: pct,\n                windowMinutes: 7 * 24 * 60,\n                resetsAt: webData.weeklyResetsAt,\n                resetDescription: webData.weeklyResetsAt.map { Self.formatResetDate($0) })\n        }\n\n        let opus: RateWindow? = webData.opusPercentUsed.map { opusPct in\n            RateWindow(\n                usedPercent: opusPct,\n                windowMinutes: 7 * 24 * 60,\n                resetsAt: webData.weeklyResetsAt,\n                resetDescription: webData.weeklyResetsAt.map { Self.formatResetDate($0) })\n        }\n\n        return ClaudeUsageSnapshot(\n            primary: primary,\n            secondary: secondary,\n            opus: opus,\n            providerCost: webData.extraUsageCost,\n            updatedAt: Date(),\n            accountEmail: webData.accountEmail,\n            accountOrganization: webData.accountOrganization,\n            loginMethod: webData.loginMethod,\n            rawText: nil)\n    }\n\n    private static func formatResetDate(_ date: Date) -> String {\n        let formatter = DateFormatter()\n        formatter.dateFormat = \"MMM d 'at' h:mma\"\n        formatter.locale = Locale(identifier: \"en_US_POSIX\")\n        return formatter.string(from: date)\n    }\n\n    // MARK: - PTY-based probe (no tmux)\n\n    private func loadViaPTY(model: String, timeout: TimeInterval = 10) async throws -> ClaudeUsageSnapshot {\n        guard let claudeBinary = ClaudeCLIResolver.resolvedBinaryPath(environment: self.environment) else {\n            throw ClaudeUsageError.claudeNotInstalled\n        }\n        let probe = ClaudeStatusProbe(\n            claudeBinary: claudeBinary,\n            timeout: timeout,\n            keepCLISessionsAlive: self.keepCLISessionsAlive)\n        let snap = try await probe.fetch()\n\n        guard let sessionPctLeft = snap.sessionPercentLeft else {\n            throw ClaudeUsageError.parseFailed(\"missing session data\")\n        }\n\n        func makeWindow(pctLeft: Int?, reset: String?) -> RateWindow? {\n            guard let left = pctLeft else { return nil }\n            let used = max(0, min(100, 100 - Double(left)))\n            let resetClean = reset?.trimmingCharacters(in: .whitespacesAndNewlines)\n            return RateWindow(\n                usedPercent: used,\n                windowMinutes: nil,\n                resetsAt: ClaudeStatusProbe.parseResetDate(from: resetClean),\n                resetDescription: resetClean)\n        }\n\n        let primary = makeWindow(pctLeft: sessionPctLeft, reset: snap.primaryResetDescription)!\n        let weekly = makeWindow(\n            pctLeft: snap.weeklyPercentLeft, reset: snap.secondaryResetDescription)\n        let opus = makeWindow(pctLeft: snap.opusPercentLeft, reset: snap.opusResetDescription)\n\n        return ClaudeUsageSnapshot(\n            primary: primary,\n            secondary: weekly,\n            opus: opus,\n            providerCost: nil,\n            updatedAt: Date(),\n            accountEmail: snap.accountEmail,\n            accountOrganization: snap.accountOrganization,\n            loginMethod: snap.loginMethod,\n            rawText: snap.rawText)\n    }\n\n    private func applyWebExtrasIfNeeded(to snapshot: ClaudeUsageSnapshot) async -> ClaudeUsageSnapshot {\n        guard self.useWebExtras, self.dataSource != .web else { return snapshot }\n        do {\n            let webData: ClaudeWebAPIFetcher.WebUsageData =\n                if let header = self.manualCookieHeader {\n                    try await ClaudeWebAPIFetcher.fetchUsage(cookieHeader: header) { msg in\n                        Self.log.debug(msg)\n                    }\n                } else {\n                    try await ClaudeWebAPIFetcher.fetchUsage(\n                        browserDetection: self.browserDetection)\n                    { msg in\n                        Self.log.debug(msg)\n                    }\n                }\n            // Only merge cost extras; keep identity fields from the primary data source.\n            if snapshot.providerCost == nil, let extra = webData.extraUsageCost {\n                return ClaudeUsageSnapshot(\n                    primary: snapshot.primary,\n                    secondary: snapshot.secondary,\n                    opus: snapshot.opus,\n                    providerCost: extra,\n                    updatedAt: snapshot.updatedAt,\n                    accountEmail: snapshot.accountEmail,\n                    accountOrganization: snapshot.accountOrganization,\n                    loginMethod: snapshot.loginMethod,\n                    rawText: snapshot.rawText)\n            }\n        } catch {\n            Self.log.debug(\"Claude web extras fetch failed: \\(error.localizedDescription)\")\n        }\n        return snapshot\n    }\n\n    // MARK: - Process helpers\n\n    private static func which(_ tool: String) -> String? {\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: \"/usr/bin/which\")\n        process.arguments = [tool]\n        let pipe = Pipe()\n        process.standardOutput = pipe\n        try? process.run()\n        process.waitUntilExit()\n        guard process.terminationStatus == 0 else { return nil }\n        let data = pipe.fileHandleForReading.readDataToEndOfFile()\n        guard\n            let path = String(data: data, encoding: .utf8)?\n                .trimmingCharacters(in: .whitespacesAndNewlines),\n                !path.isEmpty\n        else { return nil }\n        return path\n    }\n\n    private static func readString(cmd: String, args: [String]) -> String? {\n        let task = Process()\n        task.executableURL = URL(fileURLWithPath: cmd)\n        task.arguments = args\n        let pipe = Pipe()\n        task.standardOutput = pipe\n        try? task.run()\n        task.waitUntilExit()\n        guard task.terminationStatus == 0 else { return nil }\n        let data = pipe.fileHandleForReading.readDataToEndOfFile()\n        return String(data: data, encoding: .utf8)\n    }\n\n    private static func oauthCredentialProbeErrorLabel(_ error: Error) -> String {\n        guard let oauthError = error as? ClaudeOAuthCredentialsError else {\n            return String(describing: type(of: error))\n        }\n\n        return switch oauthError {\n        case .decodeFailed:\n            \"decodeFailed\"\n        case .missingOAuth:\n            \"missingOAuth\"\n        case .missingAccessToken:\n            \"missingAccessToken\"\n        case .notFound:\n            \"notFound\"\n        case let .keychainError(status):\n            \"keychainError:\\(status)\"\n        case .readFailed:\n            \"readFailed\"\n        case .refreshFailed:\n            \"refreshFailed\"\n        case .noRefreshToken:\n            \"noRefreshToken\"\n        case .refreshDelegatedToClaudeCLI:\n            \"refreshDelegatedToClaudeCLI\"\n        }\n    }\n}\n\n#if DEBUG\nextension ClaudeUsageFetcher {\n    public static func _mapOAuthUsageForTesting(\n        _ data: Data,\n        rateLimitTier: String? = nil) throws -> ClaudeUsageSnapshot\n    {\n        let usage = try ClaudeOAuthUsageFetcher.decodeUsageResponse(data)\n        let creds = ClaudeOAuthCredentials(\n            accessToken: \"test\",\n            refreshToken: nil,\n            expiresAt: Date().addingTimeInterval(3600),\n            scopes: [],\n            rateLimitTier: rateLimitTier)\n        return try Self.mapOAuthUsage(usage, credentials: creds)\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Claude/ClaudeWeb/ClaudeWebAPIFetcher.swift",
    "content": "import Foundation\nimport SweetCookieKit\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n\n/// Fetches Claude usage data directly from the claude.ai API using browser session cookies.\n///\n/// This approach mirrors what Claude Usage Tracker does, but automatically extracts the session key\n/// from browser cookies instead of requiring manual setup.\n///\n/// API endpoints used:\n/// - `GET https://claude.ai/api/organizations` → get org UUID\n/// - `GET https://claude.ai/api/organizations/{org_id}/usage` → usage percentages + reset times\npublic enum ClaudeWebAPIFetcher {\n    private static let baseURL = \"https://claude.ai/api\"\n    private static let maxProbeBytes = 200_000\n    #if os(macOS)\n    private static let cookieClient = BrowserCookieClient()\n    private static let cookieImportOrder: BrowserCookieImportOrder =\n        ProviderDefaults.metadata[.claude]?.browserCookieOrder ?? Browser.defaultImportOrder\n    #else\n    private static let cookieImportOrder: BrowserCookieImportOrder = []\n    #endif\n\n    public struct OrganizationInfo: Sendable {\n        public let id: String\n        public let name: String?\n\n        public init(id: String, name: String?) {\n            self.id = id\n            self.name = name\n        }\n    }\n\n    public struct SessionKeyInfo: Sendable {\n        public let key: String\n        public let sourceLabel: String\n        public let cookieCount: Int\n\n        public init(key: String, sourceLabel: String, cookieCount: Int) {\n            self.key = key\n            self.sourceLabel = sourceLabel\n            self.cookieCount = cookieCount\n        }\n    }\n\n    public enum FetchError: LocalizedError, Sendable {\n        case noSessionKeyFound\n        case invalidSessionKey\n        case notSupportedOnThisPlatform\n        case networkError(Error)\n        case invalidResponse\n        case unauthorized\n        case serverError(statusCode: Int)\n        case noOrganization\n\n        public var errorDescription: String? {\n            switch self {\n            case .noSessionKeyFound:\n                \"No Claude session key found in browser cookies.\"\n            case .invalidSessionKey:\n                \"Invalid Claude session key format.\"\n            case .notSupportedOnThisPlatform:\n                \"Claude web fetching is only supported on macOS.\"\n            case let .networkError(error):\n                \"Network error: \\(error.localizedDescription)\"\n            case .invalidResponse:\n                \"Invalid response from Claude API.\"\n            case .unauthorized:\n                \"Unauthorized. Your Claude session may have expired.\"\n            case let .serverError(code):\n                \"Claude API error: HTTP \\(code)\"\n            case .noOrganization:\n                \"No Claude organization found for this account.\"\n            }\n        }\n    }\n\n    /// Claude usage data from the API\n    public struct WebUsageData: Sendable {\n        public let sessionPercentUsed: Double\n        public let sessionResetsAt: Date?\n        public let weeklyPercentUsed: Double?\n        public let weeklyResetsAt: Date?\n        public let opusPercentUsed: Double?\n        public let extraUsageCost: ProviderCostSnapshot?\n        public let accountOrganization: String?\n        public let accountEmail: String?\n        public let loginMethod: String?\n\n        public init(\n            sessionPercentUsed: Double,\n            sessionResetsAt: Date?,\n            weeklyPercentUsed: Double?,\n            weeklyResetsAt: Date?,\n            opusPercentUsed: Double?,\n            extraUsageCost: ProviderCostSnapshot?,\n            accountOrganization: String?,\n            accountEmail: String?,\n            loginMethod: String?)\n        {\n            self.sessionPercentUsed = sessionPercentUsed\n            self.sessionResetsAt = sessionResetsAt\n            self.weeklyPercentUsed = weeklyPercentUsed\n            self.weeklyResetsAt = weeklyResetsAt\n            self.opusPercentUsed = opusPercentUsed\n            self.extraUsageCost = extraUsageCost\n            self.accountOrganization = accountOrganization\n            self.accountEmail = accountEmail\n            self.loginMethod = loginMethod\n        }\n    }\n\n    public struct ProbeResult: Sendable {\n        public let url: String\n        public let statusCode: Int?\n        public let contentType: String?\n        public let topLevelKeys: [String]\n        public let emails: [String]\n        public let planHints: [String]\n        public let notableFields: [String]\n        public let bodyPreview: String?\n    }\n\n    // MARK: - Public API\n\n    #if os(macOS)\n\n    /// Attempts to fetch Claude usage data using cookies extracted from browsers.\n    /// Tries browser cookies using the standard import order.\n    public static func fetchUsage(\n        browserDetection: BrowserDetection,\n        logger: ((String) -> Void)? = nil) async throws -> WebUsageData\n    {\n        let log: (String) -> Void = { msg in logger?(\"[claude-web] \\(msg)\") }\n\n        if let cached = CookieHeaderCache.load(provider: .claude),\n           !cached.cookieHeader.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty\n        {\n            log(\"Using cached cookie header from \\(cached.sourceLabel)\")\n            do {\n                return try await self.fetchUsage(cookieHeader: cached.cookieHeader, logger: log)\n            } catch let error as FetchError {\n                switch error {\n                case .unauthorized, .noSessionKeyFound, .invalidSessionKey:\n                    CookieHeaderCache.clear(provider: .claude)\n                default:\n                    throw error\n                }\n            } catch {\n                throw error\n            }\n        }\n\n        let sessionInfo = try extractSessionKeyInfo(browserDetection: browserDetection, logger: log)\n        log(\"Found session key (\\(sessionInfo.cookieCount) cookies)\")\n\n        let usage = try await self.fetchUsage(using: sessionInfo, logger: log)\n        CookieHeaderCache.store(\n            provider: .claude,\n            cookieHeader: \"sessionKey=\\(sessionInfo.key)\",\n            sourceLabel: sessionInfo.sourceLabel)\n        return usage\n    }\n\n    public static func fetchUsage(\n        cookieHeader: String,\n        logger: ((String) -> Void)? = nil) async throws -> WebUsageData\n    {\n        let log: (String) -> Void = { msg in logger?(\"[claude-web] \\(msg)\") }\n        let sessionInfo = try self.sessionKeyInfo(cookieHeader: cookieHeader)\n        log(\"Using manual session key (\\(sessionInfo.cookieCount) cookies)\")\n        return try await self.fetchUsage(using: sessionInfo, logger: log)\n    }\n\n    public static func fetchUsage(\n        using sessionKeyInfo: SessionKeyInfo,\n        logger: ((String) -> Void)? = nil) async throws -> WebUsageData\n    {\n        let log: (String) -> Void = { msg in logger?(msg) }\n        let sessionKey = sessionKeyInfo.key\n\n        // Fetch organization info\n        let organization = try await fetchOrganizationInfo(sessionKey: sessionKey, logger: log)\n        log(\"Organization resolved\")\n\n        var usage = try await fetchUsageData(orgId: organization.id, sessionKey: sessionKey, logger: log)\n        if usage.extraUsageCost == nil,\n           let extra = await fetchExtraUsageCost(orgId: organization.id, sessionKey: sessionKey, logger: log)\n        {\n            usage = WebUsageData(\n                sessionPercentUsed: usage.sessionPercentUsed,\n                sessionResetsAt: usage.sessionResetsAt,\n                weeklyPercentUsed: usage.weeklyPercentUsed,\n                weeklyResetsAt: usage.weeklyResetsAt,\n                opusPercentUsed: usage.opusPercentUsed,\n                extraUsageCost: extra,\n                accountOrganization: usage.accountOrganization,\n                accountEmail: usage.accountEmail,\n                loginMethod: usage.loginMethod)\n        }\n        if let account = await fetchAccountInfo(sessionKey: sessionKey, orgId: organization.id, logger: log) {\n            usage = WebUsageData(\n                sessionPercentUsed: usage.sessionPercentUsed,\n                sessionResetsAt: usage.sessionResetsAt,\n                weeklyPercentUsed: usage.weeklyPercentUsed,\n                weeklyResetsAt: usage.weeklyResetsAt,\n                opusPercentUsed: usage.opusPercentUsed,\n                extraUsageCost: usage.extraUsageCost,\n                accountOrganization: usage.accountOrganization,\n                accountEmail: account.email,\n                loginMethod: account.loginMethod)\n        }\n        if usage.accountOrganization == nil, let name = organization.name {\n            usage = WebUsageData(\n                sessionPercentUsed: usage.sessionPercentUsed,\n                sessionResetsAt: usage.sessionResetsAt,\n                weeklyPercentUsed: usage.weeklyPercentUsed,\n                weeklyResetsAt: usage.weeklyResetsAt,\n                opusPercentUsed: usage.opusPercentUsed,\n                extraUsageCost: usage.extraUsageCost,\n                accountOrganization: name,\n                accountEmail: usage.accountEmail,\n                loginMethod: usage.loginMethod)\n        }\n        return usage\n    }\n\n    /// Probes a list of endpoints using the current claude.ai session cookies.\n    /// - Parameters:\n    ///   - endpoints: Absolute URLs or \"/api/...\" paths. Supports \"{orgId}\" placeholder.\n    ///   - includePreview: When true, includes a truncated response preview in results.\n    public static func probeEndpoints(\n        _ endpoints: [String],\n        browserDetection: BrowserDetection,\n        includePreview: Bool = false,\n        logger: ((String) -> Void)? = nil) async throws -> [ProbeResult]\n    {\n        let log: (String) -> Void = { msg in logger?(\"[claude-probe] \\(msg)\") }\n        let sessionInfo = try extractSessionKeyInfo(browserDetection: browserDetection, logger: log)\n        let sessionKey = sessionInfo.key\n        let organization = try? await fetchOrganizationInfo(sessionKey: sessionKey, logger: log)\n        let expanded = endpoints.map { endpoint -> String in\n            var url = endpoint\n            if let orgId = organization?.id {\n                url = url.replacingOccurrences(of: \"{orgId}\", with: orgId)\n            }\n            if url.hasPrefix(\"/\") {\n                url = \"https://claude.ai\\(url)\"\n            }\n            return url\n        }\n\n        var results: [ProbeResult] = []\n        results.reserveCapacity(expanded.count)\n\n        for endpoint in expanded {\n            guard let url = URL(string: endpoint) else { continue }\n            var request = URLRequest(url: url)\n            request.setValue(\"sessionKey=\\(sessionKey)\", forHTTPHeaderField: \"Cookie\")\n            request.setValue(\"application/json, text/html;q=0.9, */*;q=0.8\", forHTTPHeaderField: \"Accept\")\n            request.httpMethod = \"GET\"\n            request.timeoutInterval = 20\n\n            do {\n                let (data, response) = try await URLSession.shared.data(for: request)\n                let http = response as? HTTPURLResponse\n                let contentType = http?.allHeaderFields[\"Content-Type\"] as? String\n                let truncated = data.prefix(Self.maxProbeBytes)\n                let body = String(data: truncated, encoding: .utf8) ?? \"\"\n\n                let parsed = Self.parseProbeBody(data: data, fallbackText: body, contentType: contentType)\n                let preview = includePreview ? parsed.preview : nil\n\n                results.append(ProbeResult(\n                    url: endpoint,\n                    statusCode: http?.statusCode,\n                    contentType: contentType,\n                    topLevelKeys: parsed.keys,\n                    emails: parsed.emails,\n                    planHints: parsed.planHints,\n                    notableFields: parsed.notableFields,\n                    bodyPreview: preview))\n            } catch {\n                results.append(ProbeResult(\n                    url: endpoint,\n                    statusCode: nil,\n                    contentType: nil,\n                    topLevelKeys: [],\n                    emails: [],\n                    planHints: [],\n                    notableFields: [],\n                    bodyPreview: \"Error: \\(error.localizedDescription)\"))\n            }\n        }\n\n        return results\n    }\n\n    /// Checks if we can find a Claude session key in browser cookies without making API calls.\n    public static func hasSessionKey(browserDetection: BrowserDetection, logger: ((String) -> Void)? = nil) -> Bool {\n        if let cached = CookieHeaderCache.load(provider: .claude),\n           self.hasSessionKey(cookieHeader: cached.cookieHeader)\n        {\n            return true\n        }\n        do {\n            _ = try self.sessionKeyInfo(browserDetection: browserDetection, logger: logger)\n            return true\n        } catch {\n            return false\n        }\n    }\n\n    public static func hasSessionKey(cookieHeader: String?) -> Bool {\n        guard let cookieHeader else { return false }\n        return (try? self.sessionKeyInfo(cookieHeader: cookieHeader)) != nil\n    }\n\n    public static func sessionKeyInfo(\n        browserDetection: BrowserDetection,\n        logger: ((String) -> Void)? = nil) throws -> SessionKeyInfo\n    {\n        try self.extractSessionKeyInfo(browserDetection: browserDetection, logger: logger)\n    }\n\n    public static func sessionKeyInfo(cookieHeader: String) throws -> SessionKeyInfo {\n        let pairs = CookieHeaderNormalizer.pairs(from: cookieHeader)\n        if let sessionKey = self.findSessionKey(in: pairs) {\n            return SessionKeyInfo(\n                key: sessionKey,\n                sourceLabel: \"Manual\",\n                cookieCount: pairs.count)\n        }\n        throw FetchError.noSessionKeyFound\n    }\n\n    // MARK: - Session Key Extraction\n\n    private static func extractSessionKeyInfo(\n        browserDetection: BrowserDetection,\n        logger: ((String) -> Void)? = nil) throws -> SessionKeyInfo\n    {\n        let log: (String) -> Void = { msg in logger?(msg) }\n\n        let cookieDomains = [\"claude.ai\"]\n\n        // Filter to cookie-eligible browsers to avoid unnecessary keychain prompts\n        let installedBrowsers = Self.cookieImportOrder.cookieImportCandidates(using: browserDetection)\n        for browserSource in installedBrowsers {\n            do {\n                let query = BrowserCookieQuery(domains: cookieDomains)\n                let sources = try Self.cookieClient.records(\n                    matching: query,\n                    in: browserSource,\n                    logger: log)\n                for source in sources {\n                    if let sessionKey = findSessionKey(in: source.records.map { record in\n                        (name: record.name, value: record.value)\n                    }) {\n                        log(\"Found sessionKey in \\(source.label)\")\n                        return SessionKeyInfo(\n                            key: sessionKey,\n                            sourceLabel: source.label,\n                            cookieCount: source.records.count)\n                    }\n                }\n            } catch {\n                BrowserCookieAccessGate.recordIfNeeded(error)\n                log(\"\\(browserSource.displayName) cookie load failed: \\(error.localizedDescription)\")\n            }\n        }\n\n        throw FetchError.noSessionKeyFound\n    }\n\n    private static func findSessionKey(in cookies: [(name: String, value: String)]) -> String? {\n        for cookie in cookies where cookie.name == \"sessionKey\" {\n            let value = cookie.value.trimmingCharacters(in: .whitespacesAndNewlines)\n            // Validate it looks like a Claude session key\n            if value.hasPrefix(\"sk-ant-\") {\n                return value\n            }\n        }\n        return nil\n    }\n\n    // MARK: - API Calls\n\n    private static func fetchOrganizationInfo(\n        sessionKey: String,\n        logger: ((String) -> Void)? = nil) async throws -> OrganizationInfo\n    {\n        let url = URL(string: \"\\(baseURL)/organizations\")!\n        var request = URLRequest(url: url)\n        request.setValue(\"sessionKey=\\(sessionKey)\", forHTTPHeaderField: \"Cookie\")\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Accept\")\n        request.httpMethod = \"GET\"\n        request.timeoutInterval = 15\n\n        let (data, response) = try await URLSession.shared.data(for: request)\n\n        guard let httpResponse = response as? HTTPURLResponse else {\n            throw FetchError.invalidResponse\n        }\n\n        logger?(\"Organizations API status: \\(httpResponse.statusCode)\")\n\n        switch httpResponse.statusCode {\n        case 200:\n            return try self.parseOrganizationResponse(data)\n        case 401, 403:\n            throw FetchError.unauthorized\n        default:\n            throw FetchError.serverError(statusCode: httpResponse.statusCode)\n        }\n    }\n\n    private static func fetchUsageData(\n        orgId: String,\n        sessionKey: String,\n        logger: ((String) -> Void)? = nil) async throws -> WebUsageData\n    {\n        let url = URL(string: \"\\(baseURL)/organizations/\\(orgId)/usage\")!\n        var request = URLRequest(url: url)\n        request.setValue(\"sessionKey=\\(sessionKey)\", forHTTPHeaderField: \"Cookie\")\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Accept\")\n        request.httpMethod = \"GET\"\n        request.timeoutInterval = 15\n\n        let (data, response) = try await URLSession.shared.data(for: request)\n\n        guard let httpResponse = response as? HTTPURLResponse else {\n            throw FetchError.invalidResponse\n        }\n\n        logger?(\"Usage API status: \\(httpResponse.statusCode)\")\n\n        switch httpResponse.statusCode {\n        case 200:\n            return try self.parseUsageResponse(data)\n        case 401, 403:\n            throw FetchError.unauthorized\n        default:\n            throw FetchError.serverError(statusCode: httpResponse.statusCode)\n        }\n    }\n\n    private static func parseUsageResponse(_ data: Data) throws -> WebUsageData {\n        guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {\n            throw FetchError.invalidResponse\n        }\n\n        // Parse five_hour (session) usage\n        var sessionPercent: Double?\n        var sessionResets: Date?\n        if let fiveHour = json[\"five_hour\"] as? [String: Any] {\n            if let utilization = fiveHour[\"utilization\"] as? Int {\n                sessionPercent = Double(utilization)\n            }\n            if let resetsAt = fiveHour[\"resets_at\"] as? String {\n                sessionResets = self.parseISO8601Date(resetsAt)\n            }\n        }\n        guard let sessionPercent else {\n            // If we can't parse session utilization, treat this as a failure so callers can fall back to the CLI.\n            throw FetchError.invalidResponse\n        }\n\n        // Parse seven_day (weekly) usage\n        var weeklyPercent: Double?\n        var weeklyResets: Date?\n        if let sevenDay = json[\"seven_day\"] as? [String: Any] {\n            if let utilization = sevenDay[\"utilization\"] as? Int {\n                weeklyPercent = Double(utilization)\n            }\n            if let resetsAt = sevenDay[\"resets_at\"] as? String {\n                weeklyResets = self.parseISO8601Date(resetsAt)\n            }\n        }\n\n        // Parse seven_day_opus (Opus-specific weekly) usage\n        var opusPercent: Double?\n        if let sevenDayOpus = json[\"seven_day_opus\"] as? [String: Any] {\n            if let utilization = sevenDayOpus[\"utilization\"] as? Int {\n                opusPercent = Double(utilization)\n            }\n        }\n\n        return WebUsageData(\n            sessionPercentUsed: sessionPercent,\n            sessionResetsAt: sessionResets,\n            weeklyPercentUsed: weeklyPercent,\n            weeklyResetsAt: weeklyResets,\n            opusPercentUsed: opusPercent,\n            extraUsageCost: nil,\n            accountOrganization: nil,\n            accountEmail: nil,\n            loginMethod: nil)\n    }\n\n    // MARK: - Extra usage cost (Claude \"Extra\")\n\n    private struct OverageSpendLimitResponse: Decodable {\n        let monthlyCreditLimit: Double?\n        let currency: String?\n        let usedCredits: Double?\n        let isEnabled: Bool?\n\n        enum CodingKeys: String, CodingKey {\n            case monthlyCreditLimit = \"monthly_credit_limit\"\n            case currency\n            case usedCredits = \"used_credits\"\n            case isEnabled = \"is_enabled\"\n        }\n    }\n\n    /// Best-effort fetch of Claude Extra spend/limit (does not fail the main usage fetch).\n    private static func fetchExtraUsageCost(\n        orgId: String,\n        sessionKey: String,\n        logger: ((String) -> Void)? = nil) async -> ProviderCostSnapshot?\n    {\n        let url = URL(string: \"\\(baseURL)/organizations/\\(orgId)/overage_spend_limit\")!\n        var request = URLRequest(url: url)\n        request.setValue(\"sessionKey=\\(sessionKey)\", forHTTPHeaderField: \"Cookie\")\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Accept\")\n        request.httpMethod = \"GET\"\n        request.timeoutInterval = 15\n\n        do {\n            let (data, response) = try await URLSession.shared.data(for: request)\n            guard let httpResponse = response as? HTTPURLResponse else { return nil }\n            logger?(\"Overage API status: \\(httpResponse.statusCode)\")\n            guard httpResponse.statusCode == 200 else { return nil }\n            return Self.parseOverageSpendLimit(data)\n        } catch {\n            return nil\n        }\n    }\n\n    private static func parseOverageSpendLimit(_ data: Data) -> ProviderCostSnapshot? {\n        guard let decoded = try? JSONDecoder().decode(OverageSpendLimitResponse.self, from: data) else { return nil }\n        guard decoded.isEnabled == true else { return nil }\n        guard let used = decoded.usedCredits,\n              let limit = decoded.monthlyCreditLimit,\n              let currency = decoded.currency,\n              !currency.isEmpty else { return nil }\n\n        let usedAmount = used / 100.0\n        let limitAmount = limit / 100.0\n\n        return ProviderCostSnapshot(\n            used: usedAmount,\n            limit: limitAmount,\n            currencyCode: currency,\n            period: \"Monthly\",\n            resetsAt: nil,\n            updatedAt: Date())\n    }\n\n    #if DEBUG\n\n    // MARK: - Test hooks (DEBUG-only)\n\n    public static func _parseUsageResponseForTesting(_ data: Data) throws -> WebUsageData {\n        try self.parseUsageResponse(data)\n    }\n\n    public static func _parseOrganizationsResponseForTesting(_ data: Data) throws -> OrganizationInfo {\n        try self.parseOrganizationResponse(data)\n    }\n\n    public static func _parseOverageSpendLimitForTesting(_ data: Data) -> ProviderCostSnapshot? {\n        self.parseOverageSpendLimit(data)\n    }\n\n    public static func _parseAccountInfoForTesting(_ data: Data, orgId: String?) -> WebAccountInfo? {\n        self.parseAccountInfo(data, orgId: orgId)\n    }\n    #endif\n\n    private static func parseISO8601Date(_ string: String) -> Date? {\n        let formatter = ISO8601DateFormatter()\n        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]\n        if let date = formatter.date(from: string) {\n            return date\n        }\n        // Try without fractional seconds\n        formatter.formatOptions = [.withInternetDateTime]\n        return formatter.date(from: string)\n    }\n\n    private struct OrganizationResponse: Decodable {\n        let uuid: String\n        let name: String?\n        let capabilities: [String]?\n\n        var normalizedCapabilities: Set<String> {\n            Set((self.capabilities ?? []).map { $0.lowercased() })\n        }\n\n        var hasChatCapability: Bool {\n            self.normalizedCapabilities.contains(\"chat\")\n        }\n\n        var isApiOnly: Bool {\n            let normalized = self.normalizedCapabilities\n            return !normalized.isEmpty && normalized == [\"api\"]\n        }\n    }\n\n    private static func parseOrganizationResponse(_ data: Data) throws -> OrganizationInfo {\n        guard let organizations = try? JSONDecoder().decode([OrganizationResponse].self, from: data) else {\n            throw FetchError.invalidResponse\n        }\n        guard let selected = organizations.first(where: { $0.hasChatCapability })\n            ?? organizations.first(where: { !$0.isApiOnly })\n            ?? organizations.first\n        else {\n            throw FetchError.noOrganization\n        }\n        let name = selected.name?.trimmingCharacters(in: .whitespacesAndNewlines)\n        let sanitized = (name?.isEmpty ?? true) ? nil : name\n        return OrganizationInfo(id: selected.uuid, name: sanitized)\n    }\n\n    public struct WebAccountInfo: Sendable {\n        public let email: String?\n        public let loginMethod: String?\n\n        public init(email: String?, loginMethod: String?) {\n            self.email = email\n            self.loginMethod = loginMethod\n        }\n    }\n\n    private struct AccountResponse: Decodable {\n        let emailAddress: String?\n        let memberships: [Membership]?\n\n        enum CodingKeys: String, CodingKey {\n            case emailAddress = \"email_address\"\n            case memberships\n        }\n\n        struct Membership: Decodable {\n            let organization: Organization\n\n            struct Organization: Decodable {\n                let uuid: String?\n                let name: String?\n                let rateLimitTier: String?\n                let billingType: String?\n\n                enum CodingKeys: String, CodingKey {\n                    case uuid\n                    case name\n                    case rateLimitTier = \"rate_limit_tier\"\n                    case billingType = \"billing_type\"\n                }\n            }\n        }\n    }\n\n    private static func fetchAccountInfo(\n        sessionKey: String,\n        orgId: String?,\n        logger: ((String) -> Void)? = nil) async -> WebAccountInfo?\n    {\n        let url = URL(string: \"\\(baseURL)/account\")!\n        var request = URLRequest(url: url)\n        request.setValue(\"sessionKey=\\(sessionKey)\", forHTTPHeaderField: \"Cookie\")\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Accept\")\n        request.httpMethod = \"GET\"\n        request.timeoutInterval = 15\n\n        do {\n            let (data, response) = try await URLSession.shared.data(for: request)\n            guard let httpResponse = response as? HTTPURLResponse else { return nil }\n            logger?(\"Account API status: \\(httpResponse.statusCode)\")\n            guard httpResponse.statusCode == 200 else { return nil }\n            return Self.parseAccountInfo(data, orgId: orgId)\n        } catch {\n            return nil\n        }\n    }\n\n    private static func parseAccountInfo(_ data: Data, orgId: String?) -> WebAccountInfo? {\n        guard let response = try? JSONDecoder().decode(AccountResponse.self, from: data) else { return nil }\n        let email = response.emailAddress?.trimmingCharacters(in: .whitespacesAndNewlines)\n        let membership = Self.selectMembership(response.memberships, orgId: orgId)\n        let plan = ClaudePlan.webLoginMethod(\n            rateLimitTier: membership?.organization.rateLimitTier,\n            billingType: membership?.organization.billingType)\n        return WebAccountInfo(email: email, loginMethod: plan)\n    }\n\n    private static func selectMembership(\n        _ memberships: [AccountResponse.Membership]?,\n        orgId: String?) -> AccountResponse.Membership?\n    {\n        guard let memberships, !memberships.isEmpty else { return nil }\n        if let orgId {\n            if let match = memberships.first(where: { $0.organization.uuid == orgId }) { return match }\n        }\n        return memberships.first\n    }\n\n    private struct ProbeParseResult: Sendable {\n        let keys: [String]\n        let emails: [String]\n        let planHints: [String]\n        let notableFields: [String]\n        let preview: String?\n    }\n\n    private static func parseProbeBody(\n        data: Data,\n        fallbackText: String,\n        contentType: String?) -> ProbeParseResult\n    {\n        let trimmed = fallbackText.trimmingCharacters(in: .whitespacesAndNewlines)\n        let looksJSON = (contentType?.lowercased().contains(\"application/json\") ?? false) ||\n            trimmed.hasPrefix(\"{\") || trimmed.hasPrefix(\"[\")\n\n        var keys: [String] = []\n        var notableFields: [String] = []\n        if looksJSON, let json = try? JSONSerialization.jsonObject(with: data) {\n            if let dict = json as? [String: Any] {\n                keys = dict.keys.sorted()\n            } else if let array = json as? [[String: Any]], let first = array.first {\n                keys = first.keys.sorted()\n            }\n            notableFields = Self.extractNotableFields(from: json)\n        }\n\n        let emails = Self.extractEmails(from: trimmed)\n        let planHints = Self.extractPlanHints(from: trimmed)\n        let preview = trimmed.isEmpty ? nil : String(trimmed.prefix(500))\n        return ProbeParseResult(\n            keys: keys,\n            emails: emails,\n            planHints: planHints,\n            notableFields: notableFields,\n            preview: preview)\n    }\n\n    private static func extractEmails(from text: String) -> [String] {\n        let pattern = #\"(?i)[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}\"#\n        guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { return [] }\n        let range = NSRange(text.startIndex..<text.endIndex, in: text)\n        var results: [String] = []\n        regex.enumerateMatches(in: text, options: [], range: range) { match, _, _ in\n            guard let match, let r = Range(match.range(at: 0), in: text) else { return }\n            let value = String(text[r]).trimmingCharacters(in: .whitespacesAndNewlines)\n            if !value.isEmpty { results.append(value) }\n        }\n        return Array(Set(results)).sorted()\n    }\n\n    private static func extractPlanHints(from text: String) -> [String] {\n        let pattern = #\"(?i)\\b(max|pro|team|ultra|enterprise)\\b\"#\n        guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { return [] }\n        let range = NSRange(text.startIndex..<text.endIndex, in: text)\n        var results: [String] = []\n        regex.enumerateMatches(in: text, options: [], range: range) { match, _, _ in\n            guard let match, let r = Range(match.range(at: 1), in: text) else { return }\n            let value = String(text[r]).trimmingCharacters(in: .whitespacesAndNewlines)\n            if !value.isEmpty { results.append(value) }\n        }\n        return Array(Set(results)).sorted()\n    }\n\n    private static func extractNotableFields(from json: Any) -> [String] {\n        let pattern = #\"(?i)(plan|tier|subscription|seat|billing|product)\"#\n        guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { return [] }\n        var results: [String] = []\n\n        func keyMatches(_ key: String) -> Bool {\n            let range = NSRange(key.startIndex..<key.endIndex, in: key)\n            return regex.firstMatch(in: key, options: [], range: range) != nil\n        }\n\n        func appendValue(_ keyPath: String, value: Any) {\n            if results.count >= 40 { return }\n            let rendered: String\n            switch value {\n            case let str as String:\n                rendered = str\n            case let num as NSNumber:\n                rendered = num.stringValue\n            case let bool as Bool:\n                rendered = bool ? \"true\" : \"false\"\n            default:\n                return\n            }\n            let trimmed = rendered.trimmingCharacters(in: .whitespacesAndNewlines)\n            guard !trimmed.isEmpty else { return }\n            results.append(\"\\(keyPath)=\\(trimmed)\")\n        }\n\n        func walk(_ value: Any, path: String) {\n            if let dict = value as? [String: Any] {\n                for (key, nested) in dict {\n                    let nextPath = path.isEmpty ? key : \"\\(path).\\(key)\"\n                    if keyMatches(key) {\n                        appendValue(nextPath, value: nested)\n                    }\n                    walk(nested, path: nextPath)\n                }\n            } else if let array = value as? [Any] {\n                for (idx, nested) in array.enumerated() {\n                    let nextPath = \"\\(path)[\\(idx)]\"\n                    walk(nested, path: nextPath)\n                }\n            }\n        }\n\n        walk(json, path: \"\")\n        return results\n    }\n\n    #else\n\n    public static func fetchUsage(logger: ((String) -> Void)? = nil) async throws -> WebUsageData {\n        throw FetchError.notSupportedOnThisPlatform\n    }\n\n    public static func fetchUsage(\n        browserDetection: BrowserDetection,\n        logger: ((String) -> Void)? = nil) async throws -> WebUsageData\n    {\n        _ = browserDetection\n        _ = logger\n        throw FetchError.notSupportedOnThisPlatform\n    }\n\n    public static func fetchUsage(\n        cookieHeader: String,\n        logger: ((String) -> Void)? = nil) async throws -> WebUsageData\n    {\n        _ = cookieHeader\n        _ = logger\n        throw FetchError.notSupportedOnThisPlatform\n    }\n\n    public static func fetchUsage(\n        using sessionKeyInfo: SessionKeyInfo,\n        logger: ((String) -> Void)? = nil) async throws -> WebUsageData\n    {\n        throw FetchError.notSupportedOnThisPlatform\n    }\n\n    public static func probeEndpoints(\n        _ endpoints: [String],\n        includePreview: Bool = false,\n        logger: ((String) -> Void)? = nil) async throws -> [ProbeResult]\n    {\n        throw FetchError.notSupportedOnThisPlatform\n    }\n\n    public static func hasSessionKey(browserDetection: BrowserDetection, logger: ((String) -> Void)? = nil) -> Bool {\n        _ = browserDetection\n        _ = logger\n        return false\n    }\n\n    public static func hasSessionKey(cookieHeader: String?) -> Bool {\n        guard let cookieHeader else { return false }\n        for pair in CookieHeaderNormalizer.pairs(from: cookieHeader) where pair.name == \"sessionKey\" {\n            let value = pair.value.trimmingCharacters(in: .whitespacesAndNewlines)\n            if value.hasPrefix(\"sk-ant-\") {\n                return true\n            }\n        }\n        return false\n    }\n\n    public static func sessionKeyInfo(logger: ((String) -> Void)? = nil) throws -> SessionKeyInfo {\n        throw FetchError.notSupportedOnThisPlatform\n    }\n\n    #endif\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Codex/CodexCLISession.swift",
    "content": "#if canImport(Darwin)\nimport Darwin\n#else\nimport Glibc\n#endif\nimport Foundation\n\nactor CodexCLISession {\n    static let shared = CodexCLISession()\n\n    enum SessionError: LocalizedError {\n        case launchFailed(String)\n        case timedOut\n        case processExited\n\n        var errorDescription: String? {\n            switch self {\n            case let .launchFailed(msg): \"Failed to launch Codex CLI session: \\(msg)\"\n            case .timedOut: \"Codex CLI session timed out.\"\n            case .processExited: \"Codex CLI session exited.\"\n            }\n        }\n    }\n\n    private var process: Process?\n    private var primaryFD: Int32 = -1\n    private var primaryHandle: FileHandle?\n    private var secondaryHandle: FileHandle?\n    private var processGroup: pid_t?\n    private var binaryPath: String?\n    private var startedAt: Date?\n    private var ptyRows: UInt16 = 0\n    private var ptyCols: UInt16 = 0\n\n    private struct RollingBuffer {\n        private let maxNeedle: Int\n        private var tail = Data()\n\n        init(maxNeedle: Int) {\n            self.maxNeedle = max(0, maxNeedle)\n        }\n\n        mutating func append(_ data: Data) -> Data {\n            guard !data.isEmpty else { return Data() }\n            var combined = Data()\n            combined.reserveCapacity(self.tail.count + data.count)\n            combined.append(self.tail)\n            combined.append(data)\n            if self.maxNeedle > 1 {\n                if combined.count >= self.maxNeedle - 1 {\n                    self.tail = combined.suffix(self.maxNeedle - 1)\n                } else {\n                    self.tail = combined\n                }\n            } else {\n                self.tail.removeAll(keepingCapacity: true)\n            }\n            return combined\n        }\n\n        mutating func reset() {\n            self.tail.removeAll(keepingCapacity: true)\n        }\n    }\n\n    static func lowercasedASCII(_ data: Data) -> Data {\n        guard !data.isEmpty else { return data }\n        var out = Data(count: data.count)\n        out.withUnsafeMutableBytes { dest in\n            data.withUnsafeBytes { source in\n                let src = source.bindMemory(to: UInt8.self)\n                let dst = dest.bindMemory(to: UInt8.self)\n                for idx in 0..<src.count {\n                    var byte = src[idx]\n                    if byte >= 65, byte <= 90 { byte += 32 }\n                    dst[idx] = byte\n                }\n            }\n        }\n        return out\n    }\n\n    // swiftlint:disable cyclomatic_complexity\n    func captureStatus(binary: String, timeout: TimeInterval, rows: UInt16, cols: UInt16) async throws -> String {\n        try self.ensureStarted(binary: binary, rows: rows, cols: cols)\n        if let startedAt {\n            let sinceStart = Date().timeIntervalSince(startedAt)\n            if sinceStart < 0.4 {\n                let delay = UInt64((0.4 - sinceStart) * 1_000_000_000)\n                try await Task.sleep(nanoseconds: delay)\n            }\n        }\n        self.drainOutput()\n\n        let script = \"/status\"\n        let cursorQuery = Data([0x1B, 0x5B, 0x36, 0x6E])\n        let statusMarkers = [\n            \"Credits:\",\n            \"5h limit\",\n            \"5-hour limit\",\n            \"Weekly limit\",\n        ].map { Data($0.utf8) }\n        let updateNeedles = [\"Update available!\", \"Run bun install -g @openai/codex\", \"0.60.1 ->\"]\n        let updateNeedlesLower = updateNeedles.map { Data($0.lowercased().utf8) }\n        let statusNeedleLengths = statusMarkers.map(\\.count)\n        let updateNeedleLengths = updateNeedlesLower.map(\\.count)\n        let statusMaxNeedle = ([cursorQuery.count] + statusNeedleLengths).max() ?? cursorQuery.count\n        let updateMaxNeedle = updateNeedleLengths.max() ?? 0\n        var statusScanBuffer = RollingBuffer(maxNeedle: statusMaxNeedle)\n        var updateScanBuffer = RollingBuffer(maxNeedle: updateMaxNeedle)\n\n        var buffer = Data()\n        let deadline = Date().addingTimeInterval(timeout)\n        var nextCursorCheckAt = Date(timeIntervalSince1970: 0)\n\n        var skippedCodexUpdate = false\n        var sentScript = false\n        var updateSkipAttempts = 0\n        var lastEnter = Date(timeIntervalSince1970: 0)\n        var scriptSentAt: Date?\n        var resendStatusRetries = 0\n        var enterRetries = 0\n        var sawCodexStatus = false\n        var sawCodexUpdatePrompt = false\n\n        while Date() < deadline {\n            let newData = self.readChunk()\n            if !newData.isEmpty {\n                buffer.append(newData)\n            }\n            let scanData = statusScanBuffer.append(newData)\n            if Date() >= nextCursorCheckAt,\n               !scanData.isEmpty,\n               scanData.range(of: cursorQuery) != nil\n            {\n                try? self.send(\"\\u{1b}[1;1R\")\n                nextCursorCheckAt = Date().addingTimeInterval(1.0)\n            }\n            if !scanData.isEmpty, !sawCodexStatus {\n                if statusMarkers.contains(where: { scanData.range(of: $0) != nil }) {\n                    sawCodexStatus = true\n                }\n            }\n\n            if !skippedCodexUpdate, !sawCodexUpdatePrompt, !newData.isEmpty {\n                let lowerData = Self.lowercasedASCII(newData)\n                let lowerScan = updateScanBuffer.append(lowerData)\n                if updateNeedlesLower.contains(where: { lowerScan.range(of: $0) != nil }) {\n                    sawCodexUpdatePrompt = true\n                }\n            }\n\n            if !skippedCodexUpdate, sawCodexUpdatePrompt {\n                try? self.send(\"\\u{1b}[B\")\n                try await Task.sleep(nanoseconds: 120_000_000)\n                try? self.send(\"\\r\")\n                try await Task.sleep(nanoseconds: 150_000_000)\n                try? self.send(\"\\r\")\n                try? self.send(script)\n                try? self.send(\"\\r\")\n                updateSkipAttempts += 1\n                if updateSkipAttempts >= 1 {\n                    skippedCodexUpdate = true\n                    sentScript = false\n                    scriptSentAt = nil\n                    buffer.removeAll()\n                    statusScanBuffer.reset()\n                    updateScanBuffer.reset()\n                    sawCodexStatus = false\n                }\n                try await Task.sleep(nanoseconds: 300_000_000)\n            }\n\n            if !sentScript, !sawCodexUpdatePrompt || skippedCodexUpdate {\n                try? self.send(script)\n                try? self.send(\"\\r\")\n                sentScript = true\n                scriptSentAt = Date()\n                lastEnter = Date()\n                try await Task.sleep(nanoseconds: 200_000_000)\n                continue\n            }\n            if sentScript, !sawCodexStatus {\n                if Date().timeIntervalSince(lastEnter) >= 1.2, enterRetries < 6 {\n                    try? self.send(\"\\r\")\n                    enterRetries += 1\n                    lastEnter = Date()\n                    try await Task.sleep(nanoseconds: 120_000_000)\n                    continue\n                }\n                if let sentAt = scriptSentAt,\n                   Date().timeIntervalSince(sentAt) >= 3.0,\n                   resendStatusRetries < 2\n                {\n                    try? self.send(script)\n                    try? self.send(\"\\r\")\n                    resendStatusRetries += 1\n                    buffer.removeAll()\n                    statusScanBuffer.reset()\n                    updateScanBuffer.reset()\n                    sawCodexStatus = false\n                    scriptSentAt = Date()\n                    lastEnter = Date()\n                    try await Task.sleep(nanoseconds: 220_000_000)\n                    continue\n                }\n            }\n            if sawCodexStatus { break }\n            if let proc = self.process, !proc.isRunning {\n                throw SessionError.processExited\n            }\n            try await Task.sleep(nanoseconds: 120_000_000)\n        }\n\n        if sawCodexStatus {\n            let settleDeadline = Date().addingTimeInterval(2.0)\n            while Date() < settleDeadline {\n                let newData = self.readChunk()\n                if !newData.isEmpty {\n                    buffer.append(newData)\n                }\n                let scanData = statusScanBuffer.append(newData)\n                if Date() >= nextCursorCheckAt,\n                   !scanData.isEmpty,\n                   scanData.range(of: cursorQuery) != nil\n                {\n                    try? self.send(\"\\u{1b}[1;1R\")\n                    nextCursorCheckAt = Date().addingTimeInterval(1.0)\n                }\n                try await Task.sleep(nanoseconds: 100_000_000)\n            }\n        }\n\n        guard !buffer.isEmpty, let text = String(data: buffer, encoding: .utf8) else {\n            throw SessionError.timedOut\n        }\n        return text\n    }\n\n    // swiftlint:enable cyclomatic_complexity\n\n    func reset() {\n        self.cleanup()\n    }\n\n    private func ensureStarted(binary: String, rows: UInt16, cols: UInt16) throws {\n        if let proc = self.process,\n           proc.isRunning,\n           self.binaryPath == binary,\n           self.ptyRows == rows,\n           self.ptyCols == cols\n        {\n            return\n        }\n        self.cleanup()\n\n        var primaryFD: Int32 = -1\n        var secondaryFD: Int32 = -1\n        var win = winsize(ws_row: rows, ws_col: cols, ws_xpixel: 0, ws_ypixel: 0)\n        guard openpty(&primaryFD, &secondaryFD, nil, nil, &win) == 0 else {\n            throw SessionError.launchFailed(\"openpty failed\")\n        }\n        _ = fcntl(primaryFD, F_SETFL, O_NONBLOCK)\n\n        let primaryHandle = FileHandle(fileDescriptor: primaryFD, closeOnDealloc: true)\n        let secondaryHandle = FileHandle(fileDescriptor: secondaryFD, closeOnDealloc: true)\n\n        let proc = Process()\n        let resolvedURL = URL(fileURLWithPath: binary)\n        proc.executableURL = resolvedURL\n        proc.arguments = [\"-s\", \"read-only\", \"-a\", \"untrusted\"]\n        proc.standardInput = secondaryHandle\n        proc.standardOutput = secondaryHandle\n        proc.standardError = secondaryHandle\n\n        let env = TTYCommandRunner.enrichedEnvironment()\n        proc.environment = env\n\n        do {\n            try proc.run()\n        } catch {\n            try? primaryHandle.close()\n            try? secondaryHandle.close()\n            throw SessionError.launchFailed(error.localizedDescription)\n        }\n\n        let pid = proc.processIdentifier\n        var processGroup: pid_t?\n        if setpgid(pid, pid) == 0 {\n            processGroup = pid\n        }\n\n        self.process = proc\n        self.primaryFD = primaryFD\n        self.primaryHandle = primaryHandle\n        self.secondaryHandle = secondaryHandle\n        self.processGroup = processGroup\n        self.binaryPath = binary\n        self.startedAt = Date()\n        self.ptyRows = rows\n        self.ptyCols = cols\n    }\n\n    private func cleanup() {\n        if let proc = self.process, proc.isRunning, let handle = self.primaryHandle {\n            try? handle.write(contentsOf: Data(\"/exit\\n\".utf8))\n        }\n        try? self.primaryHandle?.close()\n        try? self.secondaryHandle?.close()\n\n        if let proc = self.process, proc.isRunning {\n            proc.terminate()\n        }\n        if let pgid = self.processGroup {\n            kill(-pgid, SIGTERM)\n        }\n        let waitDeadline = Date().addingTimeInterval(1.0)\n        if let proc = self.process {\n            while proc.isRunning, Date() < waitDeadline {\n                usleep(100_000)\n            }\n            if proc.isRunning {\n                if let pgid = self.processGroup {\n                    kill(-pgid, SIGKILL)\n                }\n                kill(proc.processIdentifier, SIGKILL)\n            }\n        }\n\n        self.process = nil\n        self.primaryHandle = nil\n        self.secondaryHandle = nil\n        self.primaryFD = -1\n        self.processGroup = nil\n        self.binaryPath = nil\n        self.startedAt = nil\n        self.ptyRows = 0\n        self.ptyCols = 0\n    }\n\n    private func readChunk() -> Data {\n        guard self.primaryFD >= 0 else { return Data() }\n        var appended = Data()\n        while true {\n            var tmp = [UInt8](repeating: 0, count: 8192)\n            let n = read(self.primaryFD, &tmp, tmp.count)\n            if n > 0 {\n                appended.append(contentsOf: tmp.prefix(n))\n                continue\n            }\n            break\n        }\n        return appended\n    }\n\n    private func drainOutput() {\n        _ = self.readChunk()\n    }\n\n    private func send(_ text: String) throws {\n        guard let data = text.data(using: .utf8) else { return }\n        guard let handle = self.primaryHandle else { throw SessionError.processExited }\n        try handle.write(contentsOf: data)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Codex/CodexOAuth/CodexOAuthCredentials.swift",
    "content": "import Foundation\n\npublic struct CodexOAuthCredentials: Sendable {\n    public let accessToken: String\n    public let refreshToken: String\n    public let idToken: String?\n    public let accountId: String?\n    public let lastRefresh: Date?\n\n    public init(\n        accessToken: String,\n        refreshToken: String,\n        idToken: String?,\n        accountId: String?,\n        lastRefresh: Date?)\n    {\n        self.accessToken = accessToken\n        self.refreshToken = refreshToken\n        self.idToken = idToken\n        self.accountId = accountId\n        self.lastRefresh = lastRefresh\n    }\n\n    public var needsRefresh: Bool {\n        guard let lastRefresh else { return true }\n        let eightDays: TimeInterval = 8 * 24 * 60 * 60\n        return Date().timeIntervalSince(lastRefresh) > eightDays\n    }\n}\n\npublic enum CodexOAuthCredentialsError: LocalizedError, Sendable {\n    case notFound\n    case decodeFailed(String)\n    case missingTokens\n\n    public var errorDescription: String? {\n        switch self {\n        case .notFound:\n            \"Codex auth.json not found. Run `codex` to log in.\"\n        case let .decodeFailed(message):\n            \"Failed to decode Codex credentials: \\(message)\"\n        case .missingTokens:\n            \"Codex auth.json exists but contains no tokens.\"\n        }\n    }\n}\n\npublic enum CodexOAuthCredentialsStore {\n    private static var authFilePath: URL {\n        let home = FileManager.default.homeDirectoryForCurrentUser\n        if let codexHome = ProcessInfo.processInfo.environment[\"CODEX_HOME\"]?.trimmingCharacters(\n            in: .whitespacesAndNewlines),\n            !codexHome.isEmpty\n        {\n            return URL(fileURLWithPath: codexHome).appendingPathComponent(\"auth.json\")\n        }\n        return home.appendingPathComponent(\".codex\").appendingPathComponent(\"auth.json\")\n    }\n\n    public static func load() throws -> CodexOAuthCredentials {\n        let url = self.authFilePath\n        guard FileManager.default.fileExists(atPath: url.path) else {\n            throw CodexOAuthCredentialsError.notFound\n        }\n\n        let data = try Data(contentsOf: url)\n        return try self.parse(data: data)\n    }\n\n    public static func parse(data: Data) throws -> CodexOAuthCredentials {\n        guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {\n            throw CodexOAuthCredentialsError.decodeFailed(\"Invalid JSON\")\n        }\n\n        if let apiKey = json[\"OPENAI_API_KEY\"] as? String,\n           !apiKey.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty\n        {\n            return CodexOAuthCredentials(\n                accessToken: apiKey,\n                refreshToken: \"\",\n                idToken: nil,\n                accountId: nil,\n                lastRefresh: nil)\n        }\n\n        guard let tokens = json[\"tokens\"] as? [String: Any] else {\n            throw CodexOAuthCredentialsError.missingTokens\n        }\n        guard let accessToken = tokens[\"access_token\"] as? String,\n              let refreshToken = tokens[\"refresh_token\"] as? String,\n              !accessToken.isEmpty\n        else {\n            throw CodexOAuthCredentialsError.missingTokens\n        }\n\n        let idToken = tokens[\"id_token\"] as? String\n        let accountId = tokens[\"account_id\"] as? String\n        let lastRefresh = Self.parseLastRefresh(from: json[\"last_refresh\"])\n\n        return CodexOAuthCredentials(\n            accessToken: accessToken,\n            refreshToken: refreshToken,\n            idToken: idToken,\n            accountId: accountId,\n            lastRefresh: lastRefresh)\n    }\n\n    public static func save(_ credentials: CodexOAuthCredentials) throws {\n        let url = self.authFilePath\n\n        var json: [String: Any] = [:]\n        if let data = try? Data(contentsOf: url),\n           let existing = try? JSONSerialization.jsonObject(with: data) as? [String: Any]\n        {\n            json = existing\n        }\n\n        var tokens: [String: Any] = [\n            \"access_token\": credentials.accessToken,\n            \"refresh_token\": credentials.refreshToken,\n        ]\n        if let idToken = credentials.idToken {\n            tokens[\"id_token\"] = idToken\n        }\n        if let accountId = credentials.accountId {\n            tokens[\"account_id\"] = accountId\n        }\n\n        json[\"tokens\"] = tokens\n        json[\"last_refresh\"] = ISO8601DateFormatter().string(from: Date())\n\n        let data = try JSONSerialization.data(withJSONObject: json, options: [.prettyPrinted, .sortedKeys])\n        let directory = url.deletingLastPathComponent()\n        try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)\n        try data.write(to: url, options: .atomic)\n    }\n\n    private static func parseLastRefresh(from raw: Any?) -> Date? {\n        guard let value = raw as? String, !value.isEmpty else { return nil }\n        let formatter = ISO8601DateFormatter()\n        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]\n        if let date = formatter.date(from: value) { return date }\n        formatter.formatOptions = [.withInternetDateTime]\n        return formatter.date(from: value)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Codex/CodexOAuth/CodexOAuthUsageFetcher.swift",
    "content": "import Foundation\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n\npublic struct CodexUsageResponse: Decodable, Sendable {\n    public let planType: PlanType?\n    public let rateLimit: RateLimitDetails?\n    public let credits: CreditDetails?\n\n    enum CodingKeys: String, CodingKey {\n        case planType = \"plan_type\"\n        case rateLimit = \"rate_limit\"\n        case credits\n    }\n\n    public enum PlanType: Sendable, Decodable, Equatable {\n        case guest\n        case free\n        case go\n        case plus\n        case pro\n        case freeWorkspace\n        case team\n        case business\n        case education\n        case quorum\n        case k12\n        case enterprise\n        case edu\n        case unknown(String)\n\n        public var rawValue: String {\n            switch self {\n            case .guest: \"guest\"\n            case .free: \"free\"\n            case .go: \"go\"\n            case .plus: \"plus\"\n            case .pro: \"pro\"\n            case .freeWorkspace: \"free_workspace\"\n            case .team: \"team\"\n            case .business: \"business\"\n            case .education: \"education\"\n            case .quorum: \"quorum\"\n            case .k12: \"k12\"\n            case .enterprise: \"enterprise\"\n            case .edu: \"edu\"\n            case let .unknown(value): value\n            }\n        }\n\n        public init(from decoder: Decoder) throws {\n            let container = try decoder.singleValueContainer()\n            let value = try container.decode(String.self)\n            switch value {\n            case \"guest\": self = .guest\n            case \"free\": self = .free\n            case \"go\": self = .go\n            case \"plus\": self = .plus\n            case \"pro\": self = .pro\n            case \"free_workspace\": self = .freeWorkspace\n            case \"team\": self = .team\n            case \"business\": self = .business\n            case \"education\": self = .education\n            case \"quorum\": self = .quorum\n            case \"k12\": self = .k12\n            case \"enterprise\": self = .enterprise\n            case \"edu\": self = .edu\n            default:\n                self = .unknown(value)\n            }\n        }\n    }\n\n    public struct RateLimitDetails: Decodable, Sendable {\n        public let primaryWindow: WindowSnapshot?\n        public let secondaryWindow: WindowSnapshot?\n\n        enum CodingKeys: String, CodingKey {\n            case primaryWindow = \"primary_window\"\n            case secondaryWindow = \"secondary_window\"\n        }\n    }\n\n    public struct WindowSnapshot: Decodable, Sendable {\n        public let usedPercent: Int\n        public let resetAt: Int\n        public let limitWindowSeconds: Int\n\n        enum CodingKeys: String, CodingKey {\n            case usedPercent = \"used_percent\"\n            case resetAt = \"reset_at\"\n            case limitWindowSeconds = \"limit_window_seconds\"\n        }\n    }\n\n    public struct CreditDetails: Decodable, Sendable {\n        public let hasCredits: Bool\n        public let unlimited: Bool\n        public let balance: Double?\n\n        enum CodingKeys: String, CodingKey {\n            case hasCredits = \"has_credits\"\n            case unlimited\n            case balance\n        }\n\n        public init(from decoder: Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            self.hasCredits = (try? container.decode(Bool.self, forKey: .hasCredits)) ?? false\n            self.unlimited = (try? container.decode(Bool.self, forKey: .unlimited)) ?? false\n            if let balance = try? container.decode(Double.self, forKey: .balance) {\n                self.balance = balance\n            } else if let balance = try? container.decode(String.self, forKey: .balance),\n                      let value = Double(balance)\n            {\n                self.balance = value\n            } else {\n                self.balance = nil\n            }\n        }\n    }\n}\n\npublic enum CodexOAuthFetchError: LocalizedError, Sendable {\n    case unauthorized\n    case invalidResponse\n    case serverError(Int, String?)\n    case networkError(Error)\n\n    public var errorDescription: String? {\n        switch self {\n        case .unauthorized:\n            return \"Codex OAuth token expired or invalid. Run `codex` to re-authenticate.\"\n        case .invalidResponse:\n            return \"Invalid response from Codex usage API.\"\n        case let .serverError(code, message):\n            if let message, !message.isEmpty {\n                return \"Codex API error \\(code): \\(message)\"\n            }\n            return \"Codex API error \\(code).\"\n        case let .networkError(error):\n            return \"Network error: \\(error.localizedDescription)\"\n        }\n    }\n}\n\npublic enum CodexOAuthUsageFetcher {\n    private static let defaultChatGPTBaseURL = \"https://chatgpt.com/backend-api/\"\n    private static let chatGPTUsagePath = \"/wham/usage\"\n    private static let codexUsagePath = \"/api/codex/usage\"\n\n    public static func fetchUsage(accessToken: String, accountId: String?) async throws -> CodexUsageResponse {\n        var request = URLRequest(url: Self.resolveUsageURL())\n        request.httpMethod = \"GET\"\n        request.timeoutInterval = 30\n        request.setValue(\"Bearer \\(accessToken)\", forHTTPHeaderField: \"Authorization\")\n        request.setValue(\"CodexBar\", forHTTPHeaderField: \"User-Agent\")\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Accept\")\n\n        if let accountId, !accountId.isEmpty {\n            request.setValue(accountId, forHTTPHeaderField: \"ChatGPT-Account-Id\")\n        }\n\n        do {\n            let (data, response) = try await URLSession.shared.data(for: request)\n            guard let http = response as? HTTPURLResponse else {\n                throw CodexOAuthFetchError.invalidResponse\n            }\n\n            switch http.statusCode {\n            case 200...299:\n                do {\n                    return try JSONDecoder().decode(CodexUsageResponse.self, from: data)\n                } catch {\n                    throw CodexOAuthFetchError.invalidResponse\n                }\n            case 401, 403:\n                throw CodexOAuthFetchError.unauthorized\n            default:\n                let body = String(data: data, encoding: .utf8)\n                throw CodexOAuthFetchError.serverError(http.statusCode, body)\n            }\n        } catch let error as CodexOAuthFetchError {\n            throw error\n        } catch {\n            throw CodexOAuthFetchError.networkError(error)\n        }\n    }\n\n    private static func resolveUsageURL() -> URL {\n        self.resolveUsageURL(env: ProcessInfo.processInfo.environment, configContents: nil)\n    }\n\n    private static func resolveUsageURL(env: [String: String], configContents: String?) -> URL {\n        let baseURL = self.resolveChatGPTBaseURL(env: env, configContents: configContents)\n        let normalized = self.normalizeChatGPTBaseURL(baseURL)\n        let path = normalized.contains(\"/backend-api\") ? Self.chatGPTUsagePath : Self.codexUsagePath\n        let full = normalized + path\n        return URL(string: full) ?? URL(string: Self.defaultChatGPTBaseURL + Self.chatGPTUsagePath)!\n    }\n\n    private static func resolveChatGPTBaseURL(env: [String: String], configContents: String?) -> String {\n        if let configContents, let parsed = self.parseChatGPTBaseURL(from: configContents) {\n            return parsed\n        }\n        if let contents = self.loadConfigContents(env: env),\n           let parsed = self.parseChatGPTBaseURL(from: contents)\n        {\n            return parsed\n        }\n        return Self.defaultChatGPTBaseURL\n    }\n\n    private static func normalizeChatGPTBaseURL(_ value: String) -> String {\n        var trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)\n        if trimmed.isEmpty { trimmed = Self.defaultChatGPTBaseURL }\n        while trimmed.hasSuffix(\"/\") {\n            trimmed.removeLast()\n        }\n        if trimmed.hasPrefix(\"https://chatgpt.com\") || trimmed.hasPrefix(\"https://chat.openai.com\"),\n           !trimmed.contains(\"/backend-api\")\n        {\n            trimmed += \"/backend-api\"\n        }\n        return trimmed\n    }\n\n    private static func parseChatGPTBaseURL(from contents: String) -> String? {\n        for rawLine in contents.split(whereSeparator: \\.isNewline) {\n            let line = rawLine.split(separator: \"#\", maxSplits: 1, omittingEmptySubsequences: true).first\n            let trimmed = line?.trimmingCharacters(in: .whitespacesAndNewlines) ?? \"\"\n            guard !trimmed.isEmpty else { continue }\n            let parts = trimmed.split(separator: \"=\", maxSplits: 1, omittingEmptySubsequences: true)\n            guard parts.count == 2 else { continue }\n            let key = parts[0].trimmingCharacters(in: .whitespacesAndNewlines)\n            guard key == \"chatgpt_base_url\" else { continue }\n            var value = parts[1].trimmingCharacters(in: .whitespacesAndNewlines)\n            if value.hasPrefix(\"\\\"\"), value.hasSuffix(\"\\\"\") {\n                value = String(value.dropFirst().dropLast())\n            } else if value.hasPrefix(\"'\"), value.hasSuffix(\"'\") {\n                value = String(value.dropFirst().dropLast())\n            }\n            return value.trimmingCharacters(in: .whitespacesAndNewlines)\n        }\n        return nil\n    }\n\n    private static func loadConfigContents(env: [String: String]) -> String? {\n        let home = FileManager.default.homeDirectoryForCurrentUser\n        let codexHome = env[\"CODEX_HOME\"]?.trimmingCharacters(in: .whitespacesAndNewlines)\n        let root = (codexHome?.isEmpty == false) ? URL(fileURLWithPath: codexHome!) : home\n            .appendingPathComponent(\".codex\")\n        let url = root.appendingPathComponent(\"config.toml\")\n        return try? String(contentsOf: url, encoding: .utf8)\n    }\n}\n\n#if DEBUG\nextension CodexOAuthUsageFetcher {\n    static func _resolveUsageURLForTesting(env: [String: String] = [:], configContents: String? = nil) -> URL {\n        self.resolveUsageURL(env: env, configContents: configContents)\n    }\n\n    static func _decodeUsageResponseForTesting(_ data: Data) throws -> CodexUsageResponse {\n        try JSONDecoder().decode(CodexUsageResponse.self, from: data)\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Codex/CodexOAuth/CodexTokenRefresher.swift",
    "content": "import Foundation\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n\npublic enum CodexTokenRefresher {\n    private static let refreshEndpoint = URL(string: \"https://auth.openai.com/oauth/token\")!\n    private static let clientID = \"app_EMoamEEZ73f0CkXaXp7hrann\"\n\n    public enum RefreshError: LocalizedError, Sendable {\n        case expired\n        case revoked\n        case reused\n        case networkError(Error)\n        case invalidResponse(String)\n\n        public var errorDescription: String? {\n            switch self {\n            case .expired:\n                \"Refresh token expired. Please run `codex` to log in again.\"\n            case .revoked:\n                \"Refresh token was revoked. Please run `codex` to log in again.\"\n            case .reused:\n                \"Refresh token was already used. Please run `codex` to log in again.\"\n            case let .networkError(error):\n                \"Network error during token refresh: \\(error.localizedDescription)\"\n            case let .invalidResponse(message):\n                \"Invalid refresh response: \\(message)\"\n            }\n        }\n    }\n\n    public static func refresh(_ credentials: CodexOAuthCredentials) async throws -> CodexOAuthCredentials {\n        guard !credentials.refreshToken.isEmpty else {\n            return credentials\n        }\n\n        var request = URLRequest(url: Self.refreshEndpoint)\n        request.httpMethod = \"POST\"\n        request.timeoutInterval = 30\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Content-Type\")\n\n        let body: [String: String] = [\n            \"client_id\": Self.clientID,\n            \"grant_type\": \"refresh_token\",\n            \"refresh_token\": credentials.refreshToken,\n            \"scope\": \"openid profile email\",\n        ]\n        request.httpBody = try JSONSerialization.data(withJSONObject: body)\n\n        do {\n            let (data, response) = try await URLSession.shared.data(for: request)\n            guard let http = response as? HTTPURLResponse else {\n                throw RefreshError.invalidResponse(\"No HTTP response\")\n            }\n\n            if http.statusCode == 401 {\n                if let errorCode = Self.extractErrorCode(from: data) {\n                    switch errorCode.lowercased() {\n                    case \"refresh_token_expired\": throw RefreshError.expired\n                    case \"refresh_token_reused\": throw RefreshError.reused\n                    case \"refresh_token_invalidated\": throw RefreshError.revoked\n                    default: throw RefreshError.expired\n                    }\n                }\n                throw RefreshError.expired\n            }\n\n            guard http.statusCode == 200 else {\n                throw RefreshError.invalidResponse(\"Status \\(http.statusCode)\")\n            }\n\n            guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {\n                throw RefreshError.invalidResponse(\"Invalid JSON\")\n            }\n\n            let newAccessToken = json[\"access_token\"] as? String ?? credentials.accessToken\n            let newRefreshToken = json[\"refresh_token\"] as? String ?? credentials.refreshToken\n            let newIdToken = json[\"id_token\"] as? String ?? credentials.idToken\n\n            return CodexOAuthCredentials(\n                accessToken: newAccessToken,\n                refreshToken: newRefreshToken,\n                idToken: newIdToken,\n                accountId: credentials.accountId,\n                lastRefresh: Date())\n        } catch let error as RefreshError {\n            throw error\n        } catch {\n            throw RefreshError.networkError(error)\n        }\n    }\n\n    private static func extractErrorCode(from data: Data) -> String? {\n        guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { return nil }\n        if let error = json[\"error\"] as? [String: Any], let code = error[\"code\"] as? String { return code }\n        if let error = json[\"error\"] as? String { return error }\n        return json[\"code\"] as? String\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Codex/CodexProviderDescriptor.swift",
    "content": "import CodexBarMacroSupport\nimport Foundation\n\n@ProviderDescriptorRegistration\n@ProviderDescriptorDefinition\npublic enum CodexProviderDescriptor {\n    static func makeDescriptor() -> ProviderDescriptor {\n        ProviderDescriptor(\n            id: .codex,\n            metadata: ProviderMetadata(\n                id: .codex,\n                displayName: \"Codex\",\n                sessionLabel: \"Session\",\n                weeklyLabel: \"Weekly\",\n                opusLabel: nil,\n                supportsOpus: false,\n                supportsCredits: true,\n                creditsHint: \"Credits unavailable; keep Codex running to refresh.\",\n                toggleTitle: \"Show Codex usage\",\n                cliName: \"codex\",\n                defaultEnabled: true,\n                isPrimaryProvider: true,\n                usesAccountFallback: true,\n                browserCookieOrder: ProviderBrowserCookieDefaults.defaultImportOrder,\n                dashboardURL: \"https://chatgpt.com/codex/settings/usage\",\n                statusPageURL: \"https://status.openai.com/\"),\n            branding: ProviderBranding(\n                iconStyle: .codex,\n                iconResourceName: \"ProviderIcon-codex\",\n                color: ProviderColor(red: 73 / 255, green: 163 / 255, blue: 176 / 255)),\n            tokenCost: ProviderTokenCostConfig(\n                supportsTokenCost: true,\n                noDataMessage: self.noDataMessage),\n            fetchPlan: ProviderFetchPlan(\n                sourceModes: [.auto, .web, .cli, .oauth],\n                pipeline: ProviderFetchPipeline(resolveStrategies: self.resolveStrategies)),\n            cli: ProviderCLIConfig(\n                name: \"codex\",\n                versionDetector: { _ in ProviderVersionDetector.codexVersion() }))\n    }\n\n    private static func resolveStrategies(context: ProviderFetchContext) async -> [any ProviderFetchStrategy] {\n        let cli = CodexCLIUsageStrategy()\n        let oauth = CodexOAuthFetchStrategy()\n        let web = CodexWebDashboardStrategy()\n\n        switch context.runtime {\n        case .cli:\n            switch context.sourceMode {\n            case .oauth:\n                return [oauth]\n            case .web:\n                return [web]\n            case .cli:\n                return [cli]\n            case .api:\n                return []\n            case .auto:\n                return [web, cli]\n            }\n        case .app:\n            switch context.sourceMode {\n            case .oauth:\n                return [oauth]\n            case .cli:\n                return [cli]\n            case .web:\n                return [web]\n            case .api:\n                return []\n            case .auto:\n                return [oauth, cli]\n            }\n        }\n    }\n\n    private static func noDataMessage() -> String {\n        let fm = FileManager.default\n        let home = fm.homeDirectoryForCurrentUser.path\n        let base = ProcessInfo.processInfo.environment[\"CODEX_HOME\"].flatMap { raw -> String? in\n            let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)\n            guard !trimmed.isEmpty else { return nil }\n            return trimmed\n        } ?? \"\\(home)/.codex\"\n        let sessions = \"\\(base)/sessions\"\n        let archived = \"\\(base)/archived_sessions\"\n        return \"No Codex sessions found in \\(sessions) or \\(archived).\"\n    }\n\n    public static func resolveUsageStrategy(\n        selectedDataSource: CodexUsageDataSource,\n        hasOAuthCredentials: Bool) -> CodexUsageStrategy\n    {\n        if selectedDataSource == .auto {\n            if hasOAuthCredentials {\n                return CodexUsageStrategy(dataSource: .oauth)\n            }\n            return CodexUsageStrategy(dataSource: .cli)\n        }\n        return CodexUsageStrategy(dataSource: selectedDataSource)\n    }\n}\n\npublic struct CodexUsageStrategy: Equatable, Sendable {\n    public let dataSource: CodexUsageDataSource\n}\n\nstruct CodexCLIUsageStrategy: ProviderFetchStrategy {\n    let id: String = \"codex.cli\"\n    let kind: ProviderFetchKind = .cli\n\n    func isAvailable(_: ProviderFetchContext) async -> Bool {\n        true\n    }\n\n    func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {\n        let keepAlive = context.settings?.debugKeepCLISessionsAlive ?? false\n        let usage = try await context.fetcher.loadLatestUsage(keepCLISessionsAlive: keepAlive)\n        let credits = await context.includeCredits\n            ? (try? context.fetcher.loadLatestCredits(keepCLISessionsAlive: keepAlive))\n            : nil\n        return self.makeResult(\n            usage: usage,\n            credits: credits,\n            sourceLabel: \"codex-cli\")\n    }\n\n    func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool {\n        false\n    }\n}\n\nstruct CodexOAuthFetchStrategy: ProviderFetchStrategy {\n    let id: String = \"codex.oauth\"\n    let kind: ProviderFetchKind = .oauth\n\n    func isAvailable(_: ProviderFetchContext) async -> Bool {\n        (try? CodexOAuthCredentialsStore.load()) != nil\n    }\n\n    func fetch(_: ProviderFetchContext) async throws -> ProviderFetchResult {\n        var credentials = try CodexOAuthCredentialsStore.load()\n\n        if credentials.needsRefresh, !credentials.refreshToken.isEmpty {\n            credentials = try await CodexTokenRefresher.refresh(credentials)\n            try CodexOAuthCredentialsStore.save(credentials)\n        }\n\n        let usage = try await CodexOAuthUsageFetcher.fetchUsage(\n            accessToken: credentials.accessToken,\n            accountId: credentials.accountId)\n\n        return self.makeResult(\n            usage: Self.mapUsage(usage, credentials: credentials),\n            credits: Self.mapCredits(usage.credits),\n            sourceLabel: \"oauth\")\n    }\n\n    func shouldFallback(on error: Error, context: ProviderFetchContext) -> Bool {\n        guard context.sourceMode == .auto else { return false }\n        return true\n    }\n\n    private static func mapUsage(_ response: CodexUsageResponse, credentials: CodexOAuthCredentials) -> UsageSnapshot {\n        let primary = Self.makeWindow(response.rateLimit?.primaryWindow)\n        let secondary = Self.makeWindow(response.rateLimit?.secondaryWindow)\n\n        let identity = ProviderIdentitySnapshot(\n            providerID: .codex,\n            accountEmail: Self.resolveAccountEmail(from: credentials),\n            accountOrganization: nil,\n            loginMethod: Self.resolvePlan(response: response, credentials: credentials))\n\n        return UsageSnapshot(\n            primary: primary ?? RateWindow(usedPercent: 0, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            secondary: secondary,\n            tertiary: nil,\n            updatedAt: Date(),\n            identity: identity)\n    }\n\n    private static func mapCredits(_ credits: CodexUsageResponse.CreditDetails?) -> CreditsSnapshot? {\n        guard let credits, let balance = credits.balance else { return nil }\n        return CreditsSnapshot(remaining: balance, events: [], updatedAt: Date())\n    }\n\n    private static func makeWindow(_ window: CodexUsageResponse.WindowSnapshot?) -> RateWindow? {\n        guard let window else { return nil }\n        let resetDate = Date(timeIntervalSince1970: TimeInterval(window.resetAt))\n        let resetDescription = UsageFormatter.resetDescription(from: resetDate)\n        return RateWindow(\n            usedPercent: Double(window.usedPercent),\n            windowMinutes: window.limitWindowSeconds / 60,\n            resetsAt: resetDate,\n            resetDescription: resetDescription)\n    }\n\n    private static func resolveAccountEmail(from credentials: CodexOAuthCredentials) -> String? {\n        guard let idToken = credentials.idToken,\n              let payload = UsageFetcher.parseJWT(idToken)\n        else {\n            return nil\n        }\n\n        let profileDict = payload[\"https://api.openai.com/profile\"] as? [String: Any]\n        let email = (payload[\"email\"] as? String) ?? (profileDict?[\"email\"] as? String)\n        return email?.trimmingCharacters(in: .whitespacesAndNewlines)\n    }\n\n    private static func resolvePlan(response: CodexUsageResponse, credentials: CodexOAuthCredentials) -> String? {\n        if let plan = response.planType?.rawValue, !plan.isEmpty { return plan }\n        guard let idToken = credentials.idToken,\n              let payload = UsageFetcher.parseJWT(idToken)\n        else {\n            return nil\n        }\n        let authDict = payload[\"https://api.openai.com/auth\"] as? [String: Any]\n        let plan = (authDict?[\"chatgpt_plan_type\"] as? String) ?? (payload[\"chatgpt_plan_type\"] as? String)\n        return plan?.trimmingCharacters(in: .whitespacesAndNewlines)\n    }\n}\n\n#if DEBUG\nextension CodexOAuthFetchStrategy {\n    static func _mapUsageForTesting(_ data: Data, credentials: CodexOAuthCredentials) throws -> UsageSnapshot {\n        let usage = try JSONDecoder().decode(CodexUsageResponse.self, from: data)\n        return Self.mapUsage(usage, credentials: credentials)\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Codex/CodexStatusProbe.swift",
    "content": "import Foundation\n\npublic struct CodexStatusSnapshot: Sendable {\n    public let credits: Double?\n    public let fiveHourPercentLeft: Int?\n    public let weeklyPercentLeft: Int?\n    public let fiveHourResetDescription: String?\n    public let weeklyResetDescription: String?\n    public let rawText: String\n\n    public init(\n        credits: Double?,\n        fiveHourPercentLeft: Int?,\n        weeklyPercentLeft: Int?,\n        fiveHourResetDescription: String?,\n        weeklyResetDescription: String?,\n        rawText: String)\n    {\n        self.credits = credits\n        self.fiveHourPercentLeft = fiveHourPercentLeft\n        self.weeklyPercentLeft = weeklyPercentLeft\n        self.fiveHourResetDescription = fiveHourResetDescription\n        self.weeklyResetDescription = weeklyResetDescription\n        self.rawText = rawText\n    }\n}\n\npublic enum CodexStatusProbeError: LocalizedError, Sendable {\n    case codexNotInstalled\n    case parseFailed(String)\n    case timedOut\n    case updateRequired(String)\n\n    public var errorDescription: String? {\n        switch self {\n        case .codexNotInstalled:\n            \"Codex CLI missing. Install via `npm i -g @openai/codex` (or bun install) and restart.\"\n        case .parseFailed:\n            \"Could not parse Codex status; will retry shortly.\"\n        case .timedOut:\n            \"Codex status probe timed out.\"\n        case let .updateRequired(msg):\n            \"Codex CLI update needed: \\(msg)\"\n        }\n    }\n}\n\n/// Runs `codex` inside a PTY, sends `/status`, captures text, and parses credits/limits.\npublic struct CodexStatusProbe {\n    private static let defaultTimeoutSeconds: TimeInterval = 8.0\n    private static let parseRetryTimeoutSeconds: TimeInterval = 4.0\n\n    public var codexBinary: String = \"codex\"\n    public var timeout: TimeInterval = Self.defaultTimeoutSeconds\n    public var keepCLISessionsAlive: Bool = false\n\n    public init() {}\n\n    public init(\n        codexBinary: String = \"codex\",\n        timeout: TimeInterval = 8.0,\n        keepCLISessionsAlive: Bool = false)\n    {\n        self.codexBinary = codexBinary\n        self.timeout = timeout\n        self.keepCLISessionsAlive = keepCLISessionsAlive\n    }\n\n    public func fetch() async throws -> CodexStatusSnapshot {\n        let env = ProcessInfo.processInfo.environment\n        let resolved = BinaryLocator.resolveCodexBinary(env: env, loginPATH: LoginShellPathCache.shared.current)\n            ?? self.codexBinary\n        guard FileManager.default.isExecutableFile(atPath: resolved) || TTYCommandRunner.which(resolved) != nil else {\n            throw CodexStatusProbeError.codexNotInstalled\n        }\n        do {\n            return try await self.runAndParse(binary: resolved, rows: 60, cols: 200, timeout: self.timeout)\n        } catch let error as CodexStatusProbeError {\n            // Retry only parser-level flakes with a short second attempt.\n            switch error {\n            case .parseFailed:\n                return try await self.runAndParse(\n                    binary: resolved,\n                    rows: 70,\n                    cols: 220,\n                    timeout: Self.parseRetryTimeoutSeconds)\n            default:\n                throw error\n            }\n        }\n    }\n\n    // MARK: - Parsing\n\n    public static func parse(text: String) throws -> CodexStatusSnapshot {\n        let clean = TextParsing.stripANSICodes(text)\n        guard !clean.isEmpty else { throw CodexStatusProbeError.timedOut }\n        if clean.localizedCaseInsensitiveContains(\"data not available yet\") {\n            throw CodexStatusProbeError.parseFailed(\"data not available yet\")\n        }\n        if self.containsUpdatePrompt(clean) {\n            throw CodexStatusProbeError.updateRequired(\n                \"Run `bun install -g @openai/codex` to continue (update prompt blocking /status).\")\n        }\n        let credits = TextParsing.firstNumber(pattern: #\"Credits:\\s*([0-9][0-9.,]*)\"#, text: clean)\n        // Pull reset info from the same lines that contain the percentages.\n        let fiveLine = TextParsing.firstLine(matching: #\"5h limit[^\\n]*\"#, text: clean)\n        let weekLine = TextParsing.firstLine(matching: #\"Weekly limit[^\\n]*\"#, text: clean)\n        let fivePct = fiveLine.flatMap(TextParsing.percentLeft(fromLine:))\n        let weekPct = weekLine.flatMap(TextParsing.percentLeft(fromLine:))\n        let fiveReset = fiveLine.flatMap(TextParsing.resetString(fromLine:))\n        let weekReset = weekLine.flatMap(TextParsing.resetString(fromLine:))\n        if credits == nil, fivePct == nil, weekPct == nil {\n            throw CodexStatusProbeError.parseFailed(clean.prefix(400).description)\n        }\n        return CodexStatusSnapshot(\n            credits: credits,\n            fiveHourPercentLeft: fivePct,\n            weeklyPercentLeft: weekPct,\n            fiveHourResetDescription: fiveReset,\n            weeklyResetDescription: weekReset,\n            rawText: clean)\n    }\n\n    private func runAndParse(\n        binary: String,\n        rows: UInt16,\n        cols: UInt16,\n        timeout: TimeInterval) async throws -> CodexStatusSnapshot\n    {\n        let text: String\n        if self.keepCLISessionsAlive {\n            do {\n                text = try await CodexCLISession.shared.captureStatus(\n                    binary: binary,\n                    timeout: timeout,\n                    rows: rows,\n                    cols: cols)\n            } catch CodexCLISession.SessionError.processExited {\n                throw CodexStatusProbeError.timedOut\n            } catch CodexCLISession.SessionError.timedOut {\n                throw CodexStatusProbeError.timedOut\n            } catch CodexCLISession.SessionError.launchFailed(_) {\n                throw CodexStatusProbeError.codexNotInstalled\n            }\n        } else {\n            let runner = TTYCommandRunner()\n            let script = \"/status\\n\"\n            let result = try runner.run(\n                binary: binary,\n                send: script,\n                options: .init(\n                    rows: rows,\n                    cols: cols,\n                    timeout: timeout,\n                    extraArgs: [\"-s\", \"read-only\", \"-a\", \"untrusted\"]))\n            text = result.text\n        }\n        return try Self.parse(text: text)\n    }\n\n    private static func containsUpdatePrompt(_ text: String) -> Bool {\n        let lower = text.lowercased()\n        return lower.contains(\"update available\") && lower.contains(\"codex\")\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Codex/CodexUsageDataSource.swift",
    "content": "import Foundation\n\npublic enum CodexUsageDataSource: String, CaseIterable, Identifiable, Sendable {\n    case auto\n    case oauth\n    case cli\n\n    public var id: String {\n        self.rawValue\n    }\n\n    public var displayName: String {\n        switch self {\n        case .auto: \"Auto\"\n        case .oauth: \"OAuth API\"\n        case .cli: \"CLI (RPC/PTY)\"\n        }\n    }\n\n    public var sourceLabel: String {\n        switch self {\n        case .auto:\n            \"auto\"\n        case .oauth:\n            \"oauth\"\n        case .cli:\n            \"cli\"\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Codex/CodexWebDashboardStrategy.swift",
    "content": "#if os(macOS)\nimport AppKit\nimport Foundation\n\npublic struct CodexWebDashboardStrategy: ProviderFetchStrategy {\n    public let id: String = \"codex.web.dashboard\"\n    public let kind: ProviderFetchKind = .webDashboard\n\n    public init() {}\n\n    public func isAvailable(_ context: ProviderFetchContext) async -> Bool {\n        context.sourceMode.usesWeb\n    }\n\n    public func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {\n        // Ensure AppKit is initialized before using WebKit in a CLI.\n        await MainActor.run {\n            _ = NSApplication.shared\n        }\n\n        let accountEmail = context.fetcher.loadAccountInfo().email?\n            .trimmingCharacters(in: .whitespacesAndNewlines)\n        let options = OpenAIWebOptions(\n            timeout: context.webTimeout,\n            debugDumpHTML: context.webDebugDumpHTML,\n            verbose: context.verbose)\n        let result = try await Self.fetchOpenAIWebCodex(\n            accountEmail: accountEmail,\n            fetcher: context.fetcher,\n            options: options,\n            browserDetection: context.browserDetection)\n        return self.makeResult(\n            usage: result.usage,\n            credits: result.credits,\n            dashboard: result.dashboard,\n            sourceLabel: \"openai-web\")\n    }\n\n    public func shouldFallback(on error: Error, context: ProviderFetchContext) -> Bool {\n        guard context.sourceMode == .auto else { return false }\n        _ = error\n        return true\n    }\n}\n\nprivate struct OpenAIWebCodexResult {\n    let usage: UsageSnapshot\n    let credits: CreditsSnapshot?\n    let dashboard: OpenAIDashboardSnapshot\n}\n\nprivate enum OpenAIWebCodexError: LocalizedError {\n    case missingUsage\n\n    var errorDescription: String? {\n        switch self {\n        case .missingUsage:\n            \"OpenAI web dashboard did not include usage limits.\"\n        }\n    }\n}\n\nprivate struct OpenAIWebOptions {\n    let timeout: TimeInterval\n    let debugDumpHTML: Bool\n    let verbose: Bool\n}\n\n@MainActor\nprivate final class WebLogBuffer {\n    private var lines: [String] = []\n    private let maxCount: Int\n    private let verbose: Bool\n    private let logger = CodexBarLog.logger(LogCategories.openAIWeb)\n\n    init(maxCount: Int = 300, verbose: Bool) {\n        self.maxCount = maxCount\n        self.verbose = verbose\n    }\n\n    func append(_ line: String) {\n        self.lines.append(line)\n        if self.lines.count > self.maxCount {\n            self.lines.removeFirst(self.lines.count - self.maxCount)\n        }\n        if self.verbose {\n            self.logger.verbose(line)\n        }\n    }\n\n    func snapshot() -> [String] {\n        self.lines\n    }\n}\n\nextension CodexWebDashboardStrategy {\n    @MainActor\n    fileprivate static func fetchOpenAIWebCodex(\n        accountEmail: String?,\n        fetcher: UsageFetcher,\n        options: OpenAIWebOptions,\n        browserDetection: BrowserDetection) async throws -> OpenAIWebCodexResult\n    {\n        let logger = WebLogBuffer(verbose: options.verbose)\n        let log: @MainActor (String) -> Void = { line in\n            logger.append(line)\n        }\n        let dashboard = try await Self.fetchOpenAIWebDashboard(\n            accountEmail: accountEmail,\n            fetcher: fetcher,\n            options: options,\n            browserDetection: browserDetection,\n            logger: log)\n        guard let usage = dashboard.toUsageSnapshot(provider: .codex, accountEmail: accountEmail) else {\n            throw OpenAIWebCodexError.missingUsage\n        }\n        let credits = dashboard.toCreditsSnapshot()\n        return OpenAIWebCodexResult(usage: usage, credits: credits, dashboard: dashboard)\n    }\n\n    @MainActor\n    fileprivate static func fetchOpenAIWebDashboard(\n        accountEmail: String?,\n        fetcher: UsageFetcher,\n        options: OpenAIWebOptions,\n        browserDetection: BrowserDetection,\n        logger: @MainActor @escaping (String) -> Void) async throws -> OpenAIDashboardSnapshot\n    {\n        let trimmed = accountEmail?.trimmingCharacters(in: .whitespacesAndNewlines)\n        let fallback = fetcher.loadAccountInfo().email?.trimmingCharacters(in: .whitespacesAndNewlines)\n        let codexEmail = trimmed?.isEmpty == false ? trimmed : (fallback?.isEmpty == false ? fallback : nil)\n        let allowAnyAccount = codexEmail == nil\n\n        let importResult = try await OpenAIDashboardBrowserCookieImporter(browserDetection: browserDetection)\n            .importBestCookies(intoAccountEmail: codexEmail, allowAnyAccount: allowAnyAccount, logger: logger)\n        let effectiveEmail = codexEmail ?? importResult.signedInEmail?\n            .trimmingCharacters(in: .whitespacesAndNewlines)\n\n        let dash = try await OpenAIDashboardFetcher().loadLatestDashboard(\n            accountEmail: effectiveEmail,\n            logger: logger,\n            debugDumpHTML: options.debugDumpHTML,\n            timeout: options.timeout)\n        let cacheEmail = effectiveEmail ?? dash.signedInEmail?.trimmingCharacters(in: .whitespacesAndNewlines)\n        if let cacheEmail, !cacheEmail.isEmpty {\n            OpenAIDashboardCacheStore.save(OpenAIDashboardCache(accountEmail: cacheEmail, snapshot: dash))\n        }\n        return dash\n    }\n}\n#else\npublic struct CodexWebDashboardStrategy: ProviderFetchStrategy {\n    public let id: String = \"codex.web.dashboard\"\n    public let kind: ProviderFetchKind = .webDashboard\n\n    public init() {}\n\n    public func isAvailable(_: ProviderFetchContext) async -> Bool {\n        false\n    }\n\n    public func fetch(_: ProviderFetchContext) async throws -> ProviderFetchResult {\n        throw ProviderFetchError.noAvailableStrategy(.codex)\n    }\n\n    public func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool {\n        false\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Copilot/CopilotDeviceFlow.swift",
    "content": "import Foundation\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n\npublic struct CopilotDeviceFlow: Sendable {\n    private let clientID = \"Iv1.b507a08c87ecfe98\" // VS Code Client ID\n    private let scopes = \"read:user\"\n\n    public struct DeviceCodeResponse: Decodable, Sendable {\n        public let deviceCode: String\n        public let userCode: String\n        public let verificationUri: String\n        public let expiresIn: Int\n        public let interval: Int\n\n        enum CodingKeys: String, CodingKey {\n            case deviceCode = \"device_code\"\n            case userCode = \"user_code\"\n            case verificationUri = \"verification_uri\"\n            case expiresIn = \"expires_in\"\n            case interval\n        }\n    }\n\n    public struct AccessTokenResponse: Decodable, Sendable {\n        public let accessToken: String\n        public let tokenType: String\n        public let scope: String\n\n        enum CodingKeys: String, CodingKey {\n            case accessToken = \"access_token\"\n            case tokenType = \"token_type\"\n            case scope\n        }\n    }\n\n    public init() {}\n\n    public func requestDeviceCode() async throws -> DeviceCodeResponse {\n        let components = URLComponents(string: \"https://github.com/login/device/code\")!\n        let request = URLRequest(url: components.url!)\n\n        var postRequest = request\n        postRequest.httpMethod = \"POST\"\n        postRequest.setValue(\"application/json\", forHTTPHeaderField: \"Accept\")\n        postRequest.setValue(\"application/x-www-form-urlencoded\", forHTTPHeaderField: \"Content-Type\")\n\n        let body = [\n            \"client_id\": self.clientID,\n            \"scope\": self.scopes,\n        ]\n        postRequest.httpBody = Self.formURLEncodedBody(body)\n\n        let (data, response) = try await URLSession.shared.data(for: postRequest)\n\n        guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {\n            throw URLError(.badServerResponse)\n        }\n\n        return try JSONDecoder().decode(DeviceCodeResponse.self, from: data)\n    }\n\n    public func pollForToken(deviceCode: String, interval: Int) async throws -> String {\n        let url = URL(string: \"https://github.com/login/oauth/access_token\")!\n        var request = URLRequest(url: url)\n        request.httpMethod = \"POST\"\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Accept\")\n        request.setValue(\"application/x-www-form-urlencoded\", forHTTPHeaderField: \"Content-Type\")\n\n        let body = [\n            \"client_id\": self.clientID,\n            \"device_code\": deviceCode,\n            \"grant_type\": \"urn:ietf:params:oauth:grant-type:device_code\",\n        ]\n        request.httpBody = Self.formURLEncodedBody(body)\n\n        while true {\n            try await Task.sleep(nanoseconds: UInt64(interval) * 1_000_000_000)\n            try Task.checkCancellation()\n\n            let (data, _) = try await URLSession.shared.data(for: request)\n\n            // Check for error in JSON\n            if let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],\n               let error = json[\"error\"] as? String\n            {\n                if error == \"authorization_pending\" {\n                    continue\n                }\n                if error == \"slow_down\" {\n                    try await Task.sleep(nanoseconds: 5_000_000_000) // Add 5s\n                    continue\n                }\n                if error == \"expired_token\" {\n                    throw URLError(.timedOut)\n                }\n                throw URLError(.userAuthenticationRequired) // Generic failure\n            }\n\n            if let tokenResponse = try? JSONDecoder().decode(AccessTokenResponse.self, from: data) {\n                return tokenResponse.accessToken\n            }\n        }\n    }\n\n    private static func formURLEncodedBody(_ parameters: [String: String]) -> Data {\n        let pairs = parameters\n            .map { key, value in\n                \"\\(Self.formEncode(key))=\\(Self.formEncode(value))\"\n            }\n            .joined(separator: \"&\")\n        return Data(pairs.utf8)\n    }\n\n    private static func formEncode(_ value: String) -> String {\n        var allowed = CharacterSet.urlQueryAllowed\n        allowed.remove(charactersIn: \"+&=\")\n        return value.addingPercentEncoding(withAllowedCharacters: allowed) ?? value\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Copilot/CopilotProviderDescriptor.swift",
    "content": "import CodexBarMacroSupport\nimport Foundation\n\n@ProviderDescriptorRegistration\n@ProviderDescriptorDefinition\npublic enum CopilotProviderDescriptor {\n    static func makeDescriptor() -> ProviderDescriptor {\n        ProviderDescriptor(\n            id: .copilot,\n            metadata: ProviderMetadata(\n                id: .copilot,\n                displayName: \"Copilot\",\n                sessionLabel: \"Premium\",\n                weeklyLabel: \"Chat\",\n                opusLabel: nil,\n                supportsOpus: false,\n                supportsCredits: false,\n                creditsHint: \"\",\n                toggleTitle: \"Show Copilot usage\",\n                cliName: \"copilot\",\n                defaultEnabled: false,\n                isPrimaryProvider: false,\n                usesAccountFallback: false,\n                dashboardURL: \"https://github.com/settings/copilot\",\n                statusPageURL: \"https://www.githubstatus.com/\"),\n            branding: ProviderBranding(\n                iconStyle: .copilot,\n                iconResourceName: \"ProviderIcon-copilot\",\n                color: ProviderColor(red: 168 / 255, green: 85 / 255, blue: 247 / 255)),\n            tokenCost: ProviderTokenCostConfig(\n                supportsTokenCost: false,\n                noDataMessage: { \"Copilot cost summary is not supported.\" }),\n            fetchPlan: ProviderFetchPlan(\n                sourceModes: [.auto, .api],\n                pipeline: ProviderFetchPipeline(resolveStrategies: { _ in [CopilotAPIFetchStrategy()] })),\n            cli: ProviderCLIConfig(\n                name: \"copilot\",\n                versionDetector: nil))\n    }\n}\n\nstruct CopilotAPIFetchStrategy: ProviderFetchStrategy {\n    let id: String = \"copilot.api\"\n    let kind: ProviderFetchKind = .apiToken\n\n    func isAvailable(_ context: ProviderFetchContext) async -> Bool {\n        Self.resolveToken(environment: context.env) != nil\n    }\n\n    func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {\n        guard let token = Self.resolveToken(environment: context.env), !token.isEmpty else {\n            throw URLError(.userAuthenticationRequired)\n        }\n        let fetcher = CopilotUsageFetcher(token: token)\n        let snap = try await fetcher.fetch()\n        return self.makeResult(\n            usage: snap,\n            sourceLabel: \"api\")\n    }\n\n    func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool {\n        false\n    }\n\n    private static func resolveToken(environment: [String: String]) -> String? {\n        ProviderTokenResolver.copilotToken(environment: environment)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Copilot/CopilotUsageFetcher.swift",
    "content": "import Foundation\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n\npublic struct CopilotUsageFetcher: Sendable {\n    private let token: String\n\n    public init(token: String) {\n        self.token = token\n    }\n\n    public func fetch() async throws -> UsageSnapshot {\n        guard let url = URL(string: \"https://api.github.com/copilot_internal/user\") else {\n            throw URLError(.badURL)\n        }\n\n        var request = URLRequest(url: url)\n        // Use the GitHub OAuth token directly, not the Copilot token.\n        request.setValue(\"token \\(self.token)\", forHTTPHeaderField: \"Authorization\")\n        self.addCommonHeaders(to: &request)\n\n        let (data, response) = try await URLSession.shared.data(for: request)\n\n        guard let httpResponse = response as? HTTPURLResponse else {\n            throw URLError(.badServerResponse)\n        }\n\n        if httpResponse.statusCode == 401 || httpResponse.statusCode == 403 {\n            throw URLError(.userAuthenticationRequired)\n        }\n\n        guard httpResponse.statusCode == 200 else {\n            throw URLError(.badServerResponse)\n        }\n\n        let usage = try JSONDecoder().decode(CopilotUsageResponse.self, from: data)\n        let premium = self.makeRateWindow(from: usage.quotaSnapshots.premiumInteractions)\n        let chat = self.makeRateWindow(from: usage.quotaSnapshots.chat)\n\n        let primary: RateWindow?\n        let secondary: RateWindow?\n        if let premium {\n            primary = premium\n            secondary = chat\n        } else if let chatWindow = chat {\n            // Keep chat in the secondary slot so provider labels remain accurate\n            // (\"Premium\" for primary, \"Chat\" for secondary) on chat-only plans.\n            primary = nil\n            secondary = chatWindow\n        } else {\n            throw URLError(.cannotDecodeRawData)\n        }\n\n        let identity = ProviderIdentitySnapshot(\n            providerID: .copilot,\n            accountEmail: nil,\n            accountOrganization: nil,\n            loginMethod: usage.copilotPlan.capitalized)\n        return UsageSnapshot(\n            primary: primary,\n            secondary: secondary,\n            tertiary: nil,\n            providerCost: nil,\n            updatedAt: Date(),\n            identity: identity)\n    }\n\n    private func addCommonHeaders(to request: inout URLRequest) {\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Accept\")\n        request.setValue(\"vscode/1.96.2\", forHTTPHeaderField: \"Editor-Version\")\n        request.setValue(\"copilot-chat/0.26.7\", forHTTPHeaderField: \"Editor-Plugin-Version\")\n        request.setValue(\"GitHubCopilotChat/0.26.7\", forHTTPHeaderField: \"User-Agent\")\n        request.setValue(\"2025-04-01\", forHTTPHeaderField: \"X-Github-Api-Version\")\n    }\n\n    private func makeRateWindow(from snapshot: CopilotUsageResponse.QuotaSnapshot?) -> RateWindow? {\n        guard let snapshot else { return nil }\n        guard !snapshot.isPlaceholder else { return nil }\n        guard snapshot.hasPercentRemaining else { return nil }\n        // percent_remaining is 0-100 based on the JSON example in the web app source\n        let usedPercent = max(0, 100 - snapshot.percentRemaining)\n\n        return RateWindow(\n            usedPercent: usedPercent,\n            windowMinutes: nil, // Not provided\n            resetsAt: nil, // Not provided per-quota in the simplified snapshot\n            resetDescription: nil)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Cursor/CursorProviderDescriptor.swift",
    "content": "import CodexBarMacroSupport\nimport Foundation\n\n@ProviderDescriptorRegistration\n@ProviderDescriptorDefinition\npublic enum CursorProviderDescriptor {\n    static func makeDescriptor() -> ProviderDescriptor {\n        ProviderDescriptor(\n            id: .cursor,\n            metadata: ProviderMetadata(\n                id: .cursor,\n                displayName: \"Cursor\",\n                sessionLabel: \"Plan\",\n                weeklyLabel: \"On-Demand\",\n                opusLabel: nil,\n                supportsOpus: false,\n                supportsCredits: true,\n                creditsHint: \"On-demand usage beyond included plan limits.\",\n                toggleTitle: \"Show Cursor usage\",\n                cliName: \"cursor\",\n                defaultEnabled: false,\n                isPrimaryProvider: false,\n                usesAccountFallback: false,\n                browserCookieOrder: ProviderBrowserCookieDefaults.defaultImportOrder,\n                dashboardURL: \"https://cursor.com/dashboard?tab=usage\",\n                statusPageURL: \"https://status.cursor.com\",\n                statusLinkURL: nil),\n            branding: ProviderBranding(\n                iconStyle: .cursor,\n                iconResourceName: \"ProviderIcon-cursor\",\n                color: ProviderColor(red: 0 / 255, green: 191 / 255, blue: 165 / 255)),\n            tokenCost: ProviderTokenCostConfig(\n                supportsTokenCost: false,\n                noDataMessage: { \"Cursor cost summary is not supported.\" }),\n            fetchPlan: ProviderFetchPlan(\n                sourceModes: [.auto, .cli],\n                pipeline: ProviderFetchPipeline(resolveStrategies: { _ in [CursorStatusFetchStrategy()] })),\n            cli: ProviderCLIConfig(\n                name: \"cursor\",\n                versionDetector: nil))\n    }\n}\n\nstruct CursorStatusFetchStrategy: ProviderFetchStrategy {\n    let id: String = \"cursor.web\"\n    let kind: ProviderFetchKind = .web\n\n    func isAvailable(_ context: ProviderFetchContext) async -> Bool {\n        guard context.settings?.cursor?.cookieSource != .off else { return false }\n        return true\n    }\n\n    func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {\n        let probe = CursorStatusProbe(browserDetection: context.browserDetection)\n        let manual = Self.manualCookieHeader(from: context)\n        let snap = try await probe.fetch(cookieHeaderOverride: manual)\n        return self.makeResult(\n            usage: snap.toUsageSnapshot(),\n            sourceLabel: \"web\")\n    }\n\n    func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool {\n        false\n    }\n\n    private static func manualCookieHeader(from context: ProviderFetchContext) -> String? {\n        guard context.settings?.cursor?.cookieSource == .manual else { return nil }\n        return CookieHeaderNormalizer.normalize(context.settings?.cursor?.manualCookieHeader)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Cursor/CursorRequestUsage.swift",
    "content": "import Foundation\n\n/// Request usage snapshot for legacy Cursor plans (request-based instead of token-based).\npublic struct CursorRequestUsage: Codable, Sendable {\n    /// Requests used this billing cycle\n    public let used: Int\n    /// Request limit (e.g., 500 for legacy enterprise plans)\n    public let limit: Int\n\n    public init(used: Int, limit: Int) {\n        self.used = used\n        self.limit = limit\n    }\n\n    public var usedPercent: Double {\n        guard self.limit > 0 else { return 0 }\n        return (Double(self.used) / Double(self.limit)) * 100\n    }\n\n    public var remainingPercent: Double {\n        max(0, 100 - self.usedPercent)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Cursor/CursorStatusProbe.swift",
    "content": "import Foundation\nimport SweetCookieKit\n\n#if os(macOS)\n\nprivate let cursorCookieImportOrder: BrowserCookieImportOrder =\n    ProviderDefaults.metadata[.cursor]?.browserCookieOrder ?? Browser.defaultImportOrder\n\n// MARK: - Cursor Cookie Importer\n\n/// Imports Cursor session cookies from browser cookies.\npublic enum CursorCookieImporter {\n    private static let cookieClient = BrowserCookieClient()\n    private static let sessionCookieNames: Set<String> = [\n        \"WorkosCursorSessionToken\",\n        \"__Secure-next-auth.session-token\",\n        \"next-auth.session-token\",\n    ]\n\n    public struct SessionInfo: Sendable {\n        public let cookies: [HTTPCookie]\n        public let sourceLabel: String\n\n        public init(cookies: [HTTPCookie], sourceLabel: String) {\n            self.cookies = cookies\n            self.sourceLabel = sourceLabel\n        }\n\n        public var cookieHeader: String {\n            self.cookies.map { \"\\($0.name)=\\($0.value)\" }.joined(separator: \"; \")\n        }\n    }\n\n    /// Attempts to import Cursor cookies using the standard browser import order.\n    public static func importSession(\n        browserDetection: BrowserDetection,\n        logger: ((String) -> Void)? = nil) throws -> SessionInfo\n    {\n        let log: (String) -> Void = { msg in logger?(\"[cursor-cookie] \\(msg)\") }\n\n        // Filter to cookie-eligible browsers to avoid unnecessary keychain prompts\n        let installedBrowsers = cursorCookieImportOrder.cookieImportCandidates(using: browserDetection)\n        let cookieDomains = [\"cursor.com\", \"cursor.sh\"]\n        for browserSource in installedBrowsers {\n            do {\n                let query = BrowserCookieQuery(domains: cookieDomains)\n                let sources = try Self.cookieClient.records(\n                    matching: query,\n                    in: browserSource,\n                    logger: log)\n                for source in sources where !source.records.isEmpty {\n                    let httpCookies = BrowserCookieClient.makeHTTPCookies(source.records, origin: query.origin)\n                    if httpCookies.contains(where: { Self.sessionCookieNames.contains($0.name) }) {\n                        log(\"Found \\(httpCookies.count) Cursor cookies in \\(source.label)\")\n                        return SessionInfo(cookies: httpCookies, sourceLabel: source.label)\n                    } else {\n                        log(\"\\(source.label) cookies found, but no Cursor session cookie present\")\n                    }\n                }\n            } catch {\n                BrowserCookieAccessGate.recordIfNeeded(error)\n                log(\"\\(browserSource.displayName) cookie import failed: \\(error.localizedDescription)\")\n            }\n        }\n\n        throw CursorStatusProbeError.noSessionCookie\n    }\n\n    /// Check if Cursor session cookies are available\n    public static func hasSession(browserDetection: BrowserDetection, logger: ((String) -> Void)? = nil) -> Bool {\n        do {\n            let session = try self.importSession(browserDetection: browserDetection, logger: logger)\n            return !session.cookies.isEmpty\n        } catch {\n            return false\n        }\n    }\n}\n\n// MARK: - Cursor API Models\n\npublic struct CursorUsageSummary: Codable, Sendable {\n    public let billingCycleStart: String?\n    public let billingCycleEnd: String?\n    public let membershipType: String?\n    public let limitType: String?\n    public let isUnlimited: Bool?\n    public let autoModelSelectedDisplayMessage: String?\n    public let namedModelSelectedDisplayMessage: String?\n    public let individualUsage: CursorIndividualUsage?\n    public let teamUsage: CursorTeamUsage?\n}\n\npublic struct CursorIndividualUsage: Codable, Sendable {\n    public let plan: CursorPlanUsage?\n    public let onDemand: CursorOnDemandUsage?\n}\n\npublic struct CursorPlanUsage: Codable, Sendable {\n    public let enabled: Bool?\n    /// Usage in cents (e.g., 2000 = $20.00)\n    public let used: Int?\n    /// Limit in cents (e.g., 2000 = $20.00)\n    public let limit: Int?\n    /// Remaining in cents\n    public let remaining: Int?\n    public let breakdown: CursorPlanBreakdown?\n    public let autoPercentUsed: Double?\n    public let apiPercentUsed: Double?\n    public let totalPercentUsed: Double?\n}\n\npublic struct CursorPlanBreakdown: Codable, Sendable {\n    public let included: Int?\n    public let bonus: Int?\n    public let total: Int?\n}\n\npublic struct CursorOnDemandUsage: Codable, Sendable {\n    public let enabled: Bool?\n    /// Usage in cents\n    public let used: Int?\n    /// Limit in cents (nil if unlimited)\n    public let limit: Int?\n    /// Remaining in cents (nil if unlimited)\n    public let remaining: Int?\n}\n\npublic struct CursorTeamUsage: Codable, Sendable {\n    public let onDemand: CursorOnDemandUsage?\n}\n\n// MARK: - Cursor Usage API Models (Legacy Request-Based Plans)\n\n/// Response from `/api/usage?user=ID` endpoint for legacy request-based plans.\npublic struct CursorUsageResponse: Codable, Sendable {\n    public let gpt4: CursorModelUsage?\n    public let startOfMonth: String?\n\n    enum CodingKeys: String, CodingKey {\n        case gpt4 = \"gpt-4\"\n        case startOfMonth\n    }\n}\n\npublic struct CursorModelUsage: Codable, Sendable {\n    public let numRequests: Int?\n    public let numRequestsTotal: Int?\n    public let numTokens: Int?\n    public let maxRequestUsage: Int?\n    public let maxTokenUsage: Int?\n}\n\npublic struct CursorUserInfo: Codable, Sendable {\n    public let email: String?\n    public let emailVerified: Bool?\n    public let name: String?\n    public let sub: String?\n    public let createdAt: String?\n    public let updatedAt: String?\n    public let picture: String?\n\n    enum CodingKeys: String, CodingKey {\n        case email\n        case emailVerified = \"email_verified\"\n        case name\n        case sub\n        case createdAt = \"created_at\"\n        case updatedAt = \"updated_at\"\n        case picture\n    }\n}\n\n// MARK: - Cursor Status Snapshot\n\npublic struct CursorStatusSnapshot: Sendable {\n    /// Percentage of included plan usage (0-100)\n    public let planPercentUsed: Double\n    /// Included plan usage in USD\n    public let planUsedUSD: Double\n    /// Included plan limit in USD\n    public let planLimitUSD: Double\n    /// On-demand usage in USD\n    public let onDemandUsedUSD: Double\n    /// On-demand limit in USD (nil if unlimited)\n    public let onDemandLimitUSD: Double?\n    /// Team on-demand usage in USD (for team plans)\n    public let teamOnDemandUsedUSD: Double?\n    /// Team on-demand limit in USD\n    public let teamOnDemandLimitUSD: Double?\n    /// Billing cycle reset date\n    public let billingCycleEnd: Date?\n    /// Membership type (e.g., \"enterprise\", \"pro\", \"hobby\")\n    public let membershipType: String?\n    /// User email\n    public let accountEmail: String?\n    /// User name\n    public let accountName: String?\n    /// Raw API response for debugging\n    public let rawJSON: String?\n\n    // MARK: - Legacy Plan (Request-Based) Fields\n\n    /// Requests used this billing cycle (legacy plans only)\n    public let requestsUsed: Int?\n    /// Request limit (non-nil indicates legacy request-based plan)\n    public let requestsLimit: Int?\n\n    /// Whether this is a legacy request-based plan (vs token-based)\n    public var isLegacyRequestPlan: Bool {\n        self.requestsLimit != nil\n    }\n\n    public init(\n        planPercentUsed: Double,\n        planUsedUSD: Double,\n        planLimitUSD: Double,\n        onDemandUsedUSD: Double,\n        onDemandLimitUSD: Double?,\n        teamOnDemandUsedUSD: Double?,\n        teamOnDemandLimitUSD: Double?,\n        billingCycleEnd: Date?,\n        membershipType: String?,\n        accountEmail: String?,\n        accountName: String?,\n        rawJSON: String?,\n        requestsUsed: Int? = nil,\n        requestsLimit: Int? = nil)\n    {\n        self.planPercentUsed = planPercentUsed\n        self.planUsedUSD = planUsedUSD\n        self.planLimitUSD = planLimitUSD\n        self.onDemandUsedUSD = onDemandUsedUSD\n        self.onDemandLimitUSD = onDemandLimitUSD\n        self.teamOnDemandUsedUSD = teamOnDemandUsedUSD\n        self.teamOnDemandLimitUSD = teamOnDemandLimitUSD\n        self.billingCycleEnd = billingCycleEnd\n        self.membershipType = membershipType\n        self.accountEmail = accountEmail\n        self.accountName = accountName\n        self.rawJSON = rawJSON\n        self.requestsUsed = requestsUsed\n        self.requestsLimit = requestsLimit\n    }\n\n    /// Convert to UsageSnapshot for the common provider interface\n    public func toUsageSnapshot() -> UsageSnapshot {\n        // Primary: For legacy request-based plans, use request usage; otherwise use plan percentage\n        let primaryUsedPercent: Double = if self.isLegacyRequestPlan,\n                                            let used = self.requestsUsed,\n                                            let limit = self.requestsLimit,\n                                            limit > 0\n        {\n            (Double(used) / Double(limit)) * 100\n        } else {\n            self.planPercentUsed\n        }\n\n        let primary = RateWindow(\n            usedPercent: primaryUsedPercent,\n            windowMinutes: nil,\n            resetsAt: self.billingCycleEnd,\n            resetDescription: self.billingCycleEnd.map { Self.formatResetDate($0) })\n\n        // Always use individual on-demand values (what users see in their Cursor dashboard).\n        // Team values are aggregates across all members, not useful for individual tracking.\n        let resolvedOnDemandUsed = self.onDemandUsedUSD\n        let resolvedOnDemandLimit = self.onDemandLimitUSD\n\n        // Secondary: On-demand usage as percentage of individual limit\n        let secondary: RateWindow? = if let limit = resolvedOnDemandLimit,\n                                        limit > 0\n        {\n            RateWindow(\n                usedPercent: (resolvedOnDemandUsed / limit) * 100,\n                windowMinutes: nil,\n                resetsAt: self.billingCycleEnd,\n                resetDescription: self.billingCycleEnd.map { Self.formatResetDate($0) })\n        } else {\n            nil\n        }\n\n        // Provider cost snapshot for on-demand usage\n        let providerCost: ProviderCostSnapshot? = if resolvedOnDemandUsed > 0 {\n            ProviderCostSnapshot(\n                used: resolvedOnDemandUsed,\n                limit: resolvedOnDemandLimit ?? 0,\n                currencyCode: \"USD\",\n                period: \"monthly\",\n                resetsAt: self.billingCycleEnd,\n                updatedAt: Date())\n        } else {\n            nil\n        }\n\n        // Legacy plan request usage (when maxRequestUsage is set)\n        let cursorRequests: CursorRequestUsage? = if let used = self.requestsUsed,\n                                                     let limit = self.requestsLimit\n        {\n            CursorRequestUsage(used: used, limit: limit)\n        } else {\n            nil\n        }\n\n        let identity = ProviderIdentitySnapshot(\n            providerID: .cursor,\n            accountEmail: self.accountEmail,\n            accountOrganization: nil,\n            loginMethod: self.membershipType.map { Self.formatMembershipType($0) })\n        return UsageSnapshot(\n            primary: primary,\n            secondary: secondary,\n            tertiary: nil,\n            providerCost: providerCost,\n            cursorRequests: cursorRequests,\n            updatedAt: Date(),\n            identity: identity)\n    }\n\n    private static func formatResetDate(_ date: Date) -> String {\n        let formatter = DateFormatter()\n        formatter.dateFormat = \"MMM d 'at' h:mma\"\n        formatter.locale = Locale(identifier: \"en_US_POSIX\")\n        return \"Resets \" + formatter.string(from: date)\n    }\n\n    private static func formatMembershipType(_ type: String) -> String {\n        switch type.lowercased() {\n        case \"enterprise\":\n            \"Cursor Enterprise\"\n        case \"pro\":\n            \"Cursor Pro\"\n        case \"hobby\":\n            \"Cursor Hobby\"\n        case \"team\":\n            \"Cursor Team\"\n        default:\n            \"Cursor \\(type.capitalized)\"\n        }\n    }\n}\n\n// MARK: - Cursor Status Probe Error\n\npublic enum CursorStatusProbeError: LocalizedError, Sendable {\n    case notLoggedIn\n    case networkError(String)\n    case parseFailed(String)\n    case noSessionCookie\n\n    public var errorDescription: String? {\n        switch self {\n        case .notLoggedIn:\n            \"Not logged in to Cursor. Please log in via the CodexBar menu.\"\n        case let .networkError(msg):\n            \"Cursor API error: \\(msg)\"\n        case let .parseFailed(msg):\n            \"Could not parse Cursor usage: \\(msg)\"\n        case .noSessionCookie:\n            \"No Cursor session found. Please log in to cursor.com in \\(cursorCookieImportOrder.loginHint).\"\n        }\n    }\n}\n\n// MARK: - Cursor Session Store\n\npublic actor CursorSessionStore {\n    public static let shared = CursorSessionStore()\n\n    private var sessionCookies: [HTTPCookie] = []\n    private var hasLoadedFromDisk = false\n    private let fileURL: URL\n\n    private init() {\n        let fm = FileManager.default\n        let appSupport = fm.urls(for: .applicationSupportDirectory, in: .userDomainMask).first\n            ?? fm.temporaryDirectory\n        let dir = appSupport.appendingPathComponent(\"CodexBar\", isDirectory: true)\n        try? fm.createDirectory(at: dir, withIntermediateDirectories: true)\n        self.fileURL = dir.appendingPathComponent(\"cursor-session.json\")\n\n        // Load saved cookies on init\n        Task { await self.loadFromDiskIfNeeded() }\n    }\n\n    public func setCookies(_ cookies: [HTTPCookie]) {\n        self.hasLoadedFromDisk = true\n        self.sessionCookies = cookies\n        self.saveToDisk()\n    }\n\n    public func getCookies() -> [HTTPCookie] {\n        self.loadFromDiskIfNeeded()\n        return self.sessionCookies\n    }\n\n    public func clearCookies() {\n        self.hasLoadedFromDisk = true\n        self.sessionCookies = []\n        try? FileManager.default.removeItem(at: self.fileURL)\n    }\n\n    public func hasValidSession() -> Bool {\n        self.loadFromDiskIfNeeded()\n        return !self.sessionCookies.isEmpty\n    }\n\n    #if DEBUG\n    func resetForTesting(clearDisk: Bool = true) {\n        self.hasLoadedFromDisk = false\n        self.sessionCookies = []\n        if clearDisk {\n            try? FileManager.default.removeItem(at: self.fileURL)\n        }\n    }\n    #endif\n\n    private func loadFromDiskIfNeeded() {\n        guard !self.hasLoadedFromDisk else { return }\n        self.hasLoadedFromDisk = true\n        self.loadFromDisk()\n    }\n\n    private func saveToDisk() {\n        // Convert cookie properties to JSON-serializable format\n        // Date values must be converted to TimeInterval (Double)\n        let cookieData = self.sessionCookies.compactMap { cookie -> [String: Any]? in\n            guard let props = cookie.properties else { return nil }\n            var serializable: [String: Any] = [:]\n            for (key, value) in props {\n                let keyString = key.rawValue\n                if let date = value as? Date {\n                    // Convert Date to TimeInterval for JSON compatibility\n                    serializable[keyString] = date.timeIntervalSince1970\n                    serializable[keyString + \"_isDate\"] = true\n                } else if let url = value as? URL {\n                    serializable[keyString] = url.absoluteString\n                    serializable[keyString + \"_isURL\"] = true\n                } else if JSONSerialization.isValidJSONObject([value]) ||\n                    value is String ||\n                    value is Bool ||\n                    value is NSNumber\n                {\n                    serializable[keyString] = value\n                }\n            }\n            return serializable\n        }\n        guard !cookieData.isEmpty,\n              let data = try? JSONSerialization.data(withJSONObject: cookieData, options: [.prettyPrinted])\n        else {\n            return\n        }\n        try? data.write(to: self.fileURL)\n    }\n\n    private func loadFromDisk() {\n        guard let data = try? Data(contentsOf: self.fileURL),\n              let cookieArray = try? JSONSerialization.jsonObject(with: data) as? [[String: Any]]\n        else { return }\n\n        self.sessionCookies = cookieArray.compactMap { props in\n            // Convert back to HTTPCookiePropertyKey dictionary\n            var cookieProps: [HTTPCookiePropertyKey: Any] = [:]\n            for (key, value) in props {\n                // Skip marker keys\n                if key.hasSuffix(\"_isDate\") || key.hasSuffix(\"_isURL\") { continue }\n\n                let propKey = HTTPCookiePropertyKey(key)\n\n                // Check if this was a Date\n                if props[key + \"_isDate\"] as? Bool == true, let interval = value as? TimeInterval {\n                    cookieProps[propKey] = Date(timeIntervalSince1970: interval)\n                }\n                // Check if this was a URL\n                else if props[key + \"_isURL\"] as? Bool == true, let urlString = value as? String {\n                    cookieProps[propKey] = URL(string: urlString)\n                } else {\n                    cookieProps[propKey] = value\n                }\n            }\n            return HTTPCookie(properties: cookieProps)\n        }\n    }\n}\n\n// MARK: - Cursor Status Probe\n\npublic struct CursorStatusProbe: Sendable {\n    public let baseURL: URL\n    public var timeout: TimeInterval = 15.0\n    private let browserDetection: BrowserDetection\n\n    public init(\n        baseURL: URL = URL(string: \"https://cursor.com\")!,\n        timeout: TimeInterval = 15.0,\n        browserDetection: BrowserDetection)\n    {\n        self.baseURL = baseURL\n        self.timeout = timeout\n        self.browserDetection = browserDetection\n    }\n\n    /// Fetch Cursor usage with manual cookie header (for debugging).\n    public func fetchWithManualCookies(_ cookieHeader: String) async throws -> CursorStatusSnapshot {\n        try await self.fetchWithCookieHeader(cookieHeader)\n    }\n\n    /// Fetch Cursor usage using browser cookies with fallback to stored session.\n    public func fetch(cookieHeaderOverride: String? = nil, logger: ((String) -> Void)? = nil)\n        async throws -> CursorStatusSnapshot\n    {\n        let log: (String) -> Void = { msg in logger?(\"[cursor] \\(msg)\") }\n\n        if let override = CookieHeaderNormalizer.normalize(cookieHeaderOverride) {\n            log(\"Using manual cookie header\")\n            return try await self.fetchWithCookieHeader(override)\n        }\n\n        if let cached = CookieHeaderCache.load(provider: .cursor),\n           !cached.cookieHeader.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty\n        {\n            log(\"Using cached cookie header from \\(cached.sourceLabel)\")\n            do {\n                return try await self.fetchWithCookieHeader(cached.cookieHeader)\n            } catch let error as CursorStatusProbeError {\n                if case .notLoggedIn = error {\n                    CookieHeaderCache.clear(provider: .cursor)\n                } else {\n                    throw error\n                }\n            } catch {\n                throw error\n            }\n        }\n\n        // Try importing cookies from the configured browser order first.\n        do {\n            let session = try CursorCookieImporter.importSession(browserDetection: self.browserDetection, logger: log)\n            log(\"Using cookies from \\(session.sourceLabel)\")\n            let snapshot = try await self.fetchWithCookieHeader(session.cookieHeader)\n            CookieHeaderCache.store(\n                provider: .cursor,\n                cookieHeader: session.cookieHeader,\n                sourceLabel: session.sourceLabel)\n            return snapshot\n        } catch {\n            log(\"Browser cookie import failed: \\(error.localizedDescription)\")\n        }\n\n        // Fall back to stored session cookies (from \"Add Account\" login flow)\n        let storedCookies = await CursorSessionStore.shared.getCookies()\n        if !storedCookies.isEmpty {\n            log(\"Using stored session cookies\")\n            let cookieHeader = storedCookies.map { \"\\($0.name)=\\($0.value)\" }.joined(separator: \"; \")\n            do {\n                return try await self.fetchWithCookieHeader(cookieHeader)\n            } catch {\n                if case CursorStatusProbeError.notLoggedIn = error {\n                    // Clear only when auth is invalid; keep for transient failures.\n                    await CursorSessionStore.shared.clearCookies()\n                    log(\"Stored session invalid, cleared\")\n                } else {\n                    log(\"Stored session failed: \\(error.localizedDescription)\")\n                }\n            }\n        }\n\n        throw CursorStatusProbeError.noSessionCookie\n    }\n\n    private func fetchWithCookieHeader(_ cookieHeader: String) async throws -> CursorStatusSnapshot {\n        async let usageSummaryTask = self.fetchUsageSummary(cookieHeader: cookieHeader)\n        async let userInfoTask = self.fetchUserInfo(cookieHeader: cookieHeader)\n\n        let (usageSummary, rawJSON) = try await usageSummaryTask\n        let userInfo = try? await userInfoTask\n\n        // Fetch legacy request usage only if user has a sub ID.\n        // Uses try? to avoid breaking the flow for users where this endpoint fails or returns unexpected data.\n        var requestUsage: CursorUsageResponse?\n        var requestUsageRawJSON: String?\n        if let userId = userInfo?.sub {\n            do {\n                let (usage, usageRawJSON) = try await self.fetchRequestUsage(userId: userId, cookieHeader: cookieHeader)\n                requestUsage = usage\n                requestUsageRawJSON = usageRawJSON\n            } catch {\n                // Silently ignore - not all plans have this endpoint\n            }\n        }\n\n        // Combine raw JSON for debugging\n        var combinedRawJSON: String? = rawJSON\n        if let usageJSON = requestUsageRawJSON {\n            combinedRawJSON = (combinedRawJSON ?? \"\") + \"\\n\\n--- /api/usage response ---\\n\" + usageJSON\n        }\n\n        return self.parseUsageSummary(\n            usageSummary,\n            userInfo: userInfo,\n            rawJSON: combinedRawJSON,\n            requestUsage: requestUsage)\n    }\n\n    private func fetchUsageSummary(cookieHeader: String) async throws -> (CursorUsageSummary, String) {\n        let url = self.baseURL.appendingPathComponent(\"/api/usage-summary\")\n        var request = URLRequest(url: url)\n        request.timeoutInterval = self.timeout\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Accept\")\n        request.setValue(cookieHeader, forHTTPHeaderField: \"Cookie\")\n\n        let (data, response) = try await URLSession.shared.data(for: request)\n\n        guard let httpResponse = response as? HTTPURLResponse else {\n            throw CursorStatusProbeError.networkError(\"Invalid response\")\n        }\n\n        if httpResponse.statusCode == 401 || httpResponse.statusCode == 403 {\n            throw CursorStatusProbeError.notLoggedIn\n        }\n\n        guard httpResponse.statusCode == 200 else {\n            throw CursorStatusProbeError.networkError(\"HTTP \\(httpResponse.statusCode)\")\n        }\n\n        let rawJSON = String(data: data, encoding: .utf8) ?? \"<binary>\"\n\n        do {\n            let decoder = JSONDecoder()\n            let summary = try decoder.decode(CursorUsageSummary.self, from: data)\n            return (summary, rawJSON)\n        } catch {\n            throw CursorStatusProbeError\n                .parseFailed(\"JSON decode failed: \\(error.localizedDescription). Raw: \\(rawJSON.prefix(200))\")\n        }\n    }\n\n    private func fetchUserInfo(cookieHeader: String) async throws -> CursorUserInfo {\n        let url = self.baseURL.appendingPathComponent(\"/api/auth/me\")\n        var request = URLRequest(url: url)\n        request.timeoutInterval = self.timeout\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Accept\")\n        request.setValue(cookieHeader, forHTTPHeaderField: \"Cookie\")\n\n        let (data, response) = try await URLSession.shared.data(for: request)\n\n        guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {\n            throw CursorStatusProbeError.networkError(\"Failed to fetch user info\")\n        }\n\n        let decoder = JSONDecoder()\n        return try decoder.decode(CursorUserInfo.self, from: data)\n    }\n\n    private func fetchRequestUsage(\n        userId: String,\n        cookieHeader: String) async throws -> (CursorUsageResponse, String)\n    {\n        let url = self.baseURL.appendingPathComponent(\"/api/usage\")\n            .appending(queryItems: [URLQueryItem(name: \"user\", value: userId)])\n        var request = URLRequest(url: url)\n        request.timeoutInterval = self.timeout\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Accept\")\n        request.setValue(cookieHeader, forHTTPHeaderField: \"Cookie\")\n\n        let (data, response) = try await URLSession.shared.data(for: request)\n\n        guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {\n            throw CursorStatusProbeError.networkError(\"Failed to fetch request usage\")\n        }\n\n        let rawJSON = String(data: data, encoding: .utf8) ?? \"<binary>\"\n        let decoder = JSONDecoder()\n        let usage = try decoder.decode(CursorUsageResponse.self, from: data)\n        return (usage, rawJSON)\n    }\n\n    func parseUsageSummary(\n        _ summary: CursorUsageSummary,\n        userInfo: CursorUserInfo?,\n        rawJSON: String?,\n        requestUsage: CursorUsageResponse? = nil) -> CursorStatusSnapshot\n    {\n        // Parse billing cycle end date\n        let billingCycleEnd: Date? = summary.billingCycleEnd.flatMap { dateString in\n            let formatter = ISO8601DateFormatter()\n            formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]\n            return formatter.date(from: dateString) ?? ISO8601DateFormatter().date(from: dateString)\n        }\n\n        // Convert cents to USD (plan percent derives from raw values to avoid percent unit mismatches).\n        // Use plan.limit directly - breakdown.total represents total *used* credits, not the limit.\n        let planUsedRaw = Double(summary.individualUsage?.plan?.used ?? 0)\n        let planLimitRaw = Double(summary.individualUsage?.plan?.limit ?? 0)\n        let planUsed = planUsedRaw / 100.0\n        let planLimit = planLimitRaw / 100.0\n        let planPercentUsed: Double = if planLimitRaw > 0 {\n            (planUsedRaw / planLimitRaw) * 100\n        } else if let totalPercentUsed = summary.individualUsage?.plan?.totalPercentUsed {\n            totalPercentUsed <= 1 ? totalPercentUsed * 100 : totalPercentUsed\n        } else {\n            0\n        }\n\n        let onDemandUsed = Double(summary.individualUsage?.onDemand?.used ?? 0) / 100.0\n        let onDemandLimit: Double? = summary.individualUsage?.onDemand?.limit.map { Double($0) / 100.0 }\n\n        let teamOnDemandUsed: Double? = summary.teamUsage?.onDemand?.used.map { Double($0) / 100.0 }\n        let teamOnDemandLimit: Double? = summary.teamUsage?.onDemand?.limit.map { Double($0) / 100.0 }\n\n        // Legacy request-based plan: maxRequestUsage being non-nil indicates a request-based plan\n        let requestsUsed: Int? = requestUsage?.gpt4?.numRequestsTotal ?? requestUsage?.gpt4?.numRequests\n        let requestsLimit: Int? = requestUsage?.gpt4?.maxRequestUsage\n\n        return CursorStatusSnapshot(\n            planPercentUsed: planPercentUsed,\n            planUsedUSD: planUsed,\n            planLimitUSD: planLimit,\n            onDemandUsedUSD: onDemandUsed,\n            onDemandLimitUSD: onDemandLimit,\n            teamOnDemandUsedUSD: teamOnDemandUsed,\n            teamOnDemandLimitUSD: teamOnDemandLimit,\n            billingCycleEnd: billingCycleEnd,\n            membershipType: summary.membershipType,\n            accountEmail: userInfo?.email,\n            accountName: userInfo?.name,\n            rawJSON: rawJSON,\n            requestsUsed: requestsUsed,\n            requestsLimit: requestsLimit)\n    }\n}\n\n#else\n\n// MARK: - Cursor (Unsupported)\n\npublic enum CursorStatusProbeError: LocalizedError, Sendable {\n    case notSupported\n\n    public var errorDescription: String? {\n        \"Cursor is only supported on macOS.\"\n    }\n}\n\npublic struct CursorStatusSnapshot: Sendable {\n    public init() {}\n\n    public func toUsageSnapshot() -> UsageSnapshot {\n        UsageSnapshot(\n            primary: RateWindow(usedPercent: 0, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            secondary: nil,\n            tertiary: nil,\n            providerCost: nil,\n            updatedAt: Date(),\n            identity: nil)\n    }\n}\n\npublic struct CursorStatusProbe: Sendable {\n    public init(\n        baseURL: URL = URL(string: \"https://cursor.com\")!,\n        timeout: TimeInterval = 15.0,\n        browserDetection: BrowserDetection)\n    {\n        _ = baseURL\n        _ = timeout\n        _ = browserDetection\n    }\n\n    public func fetch(logger: ((String) -> Void)? = nil) async throws -> CursorStatusSnapshot {\n        _ = logger\n        throw CursorStatusProbeError.notSupported\n    }\n\n    public func fetch(\n        cookieHeaderOverride _: String? = nil,\n        logger: ((String) -> Void)? = nil) async throws -> CursorStatusSnapshot\n    {\n        try await self.fetch(logger: logger)\n    }\n}\n\n#endif\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Factory/FactoryLocalStorageImporter.swift",
    "content": "import Foundation\n#if os(macOS)\nimport SQLite3\nimport SweetCookieKit\n#endif\n\n#if os(macOS)\nenum FactoryLocalStorageImporter {\n    struct TokenInfo {\n        let refreshToken: String\n        let accessToken: String?\n        let organizationID: String?\n        let sourceLabel: String\n    }\n\n    static func importWorkOSTokens(\n        browserDetection: BrowserDetection,\n        logger: ((String) -> Void)? = nil) -> [TokenInfo]\n    {\n        let log: (String) -> Void = { msg in logger?(\"[factory-storage] \\(msg)\") }\n        var tokens: [TokenInfo] = []\n\n        let safariCandidates = self.safariLocalStorageCandidates()\n        let chromeCandidates = self.chromeLocalStorageCandidates(browserDetection: browserDetection)\n        if !safariCandidates.isEmpty {\n            log(\"Safari local storage candidates: \\(safariCandidates.count)\")\n        }\n        if !chromeCandidates.isEmpty {\n            log(\"Chrome local storage candidates: \\(chromeCandidates.count)\")\n        }\n\n        let candidates = safariCandidates + chromeCandidates\n        for candidate in candidates {\n            let match: WorkOSTokenMatch? = switch candidate.kind {\n            case let .chromeLevelDB(levelDBURL):\n                self.readWorkOSToken(from: levelDBURL)\n            case let .safariSQLite(dbURL):\n                self.readWorkOSTokenFromSafariSQLite(dbURL: dbURL, logger: log)\n            }\n            guard let token = match else { continue }\n            log(\"Found WorkOS refresh token in \\(candidate.label)\")\n            tokens.append(TokenInfo(\n                refreshToken: token.refreshToken,\n                accessToken: token.accessToken,\n                organizationID: token.organizationID,\n                sourceLabel: candidate.label))\n        }\n\n        if tokens.isEmpty {\n            log(\"No WorkOS refresh token found in browser local storage\")\n        }\n\n        return tokens\n    }\n\n    static func hasSafariWorkOSRefreshToken() -> Bool {\n        for candidate in self.safariLocalStorageCandidates() {\n            guard case let .safariSQLite(dbURL) = candidate.kind else { continue }\n            if self.readWorkOSTokenFromSafariSQLite(dbURL: dbURL) != nil {\n                return true\n            }\n        }\n        return false\n    }\n\n    // MARK: - Chrome local storage discovery\n\n    private enum LocalStorageSourceKind {\n        case chromeLevelDB(URL)\n        case safariSQLite(URL)\n    }\n\n    private struct LocalStorageCandidate {\n        let label: String\n        let kind: LocalStorageSourceKind\n    }\n\n    private static func chromeLocalStorageCandidates(browserDetection: BrowserDetection) -> [LocalStorageCandidate] {\n        let browsers: [Browser] = [\n            .chrome,\n            .chromeBeta,\n            .chromeCanary,\n            .arc,\n            .arcBeta,\n            .arcCanary,\n            .dia,\n            .chatgptAtlas,\n            .chromium,\n            .helium,\n        ]\n\n        // Filter to browsers with profile data to avoid unnecessary filesystem access.\n        let installedBrowsers = browsers.browsersWithProfileData(using: browserDetection)\n\n        let roots = ChromiumProfileLocator\n            .roots(for: installedBrowsers, homeDirectories: BrowserCookieClient.defaultHomeDirectories())\n            .map { (url: $0.url, labelPrefix: $0.labelPrefix) }\n\n        var candidates: [LocalStorageCandidate] = []\n        for root in roots {\n            candidates.append(contentsOf: self.chromeProfileLocalStorageDirs(\n                root: root.url,\n                labelPrefix: root.labelPrefix))\n        }\n        return candidates\n    }\n\n    private static func chromeProfileLocalStorageDirs(root: URL, labelPrefix: String) -> [LocalStorageCandidate] {\n        guard let entries = try? FileManager.default.contentsOfDirectory(\n            at: root,\n            includingPropertiesForKeys: [.isDirectoryKey],\n            options: [.skipsHiddenFiles])\n        else { return [] }\n\n        let profileDirs = entries.filter { url in\n            guard let isDir = (try? url.resourceValues(forKeys: [.isDirectoryKey]).isDirectory), isDir else {\n                return false\n            }\n            let name = url.lastPathComponent\n            return name == \"Default\" || name.hasPrefix(\"Profile \") || name.hasPrefix(\"user-\")\n        }\n        .sorted { $0.lastPathComponent < $1.lastPathComponent }\n\n        return profileDirs.compactMap { dir in\n            let levelDBURL = dir.appendingPathComponent(\"Local Storage\").appendingPathComponent(\"leveldb\")\n            guard FileManager.default.fileExists(atPath: levelDBURL.path) else { return nil }\n            let label = \"\\(labelPrefix) \\(dir.lastPathComponent)\"\n            return LocalStorageCandidate(label: label, kind: .chromeLevelDB(levelDBURL))\n        }\n    }\n\n    private static func safariLocalStorageCandidates() -> [LocalStorageCandidate] {\n        let root = FileManager.default.homeDirectoryForCurrentUser\n            .appendingPathComponent(\"Library\")\n            .appendingPathComponent(\"Containers\")\n            .appendingPathComponent(\"com.apple.Safari\")\n            .appendingPathComponent(\"Data\")\n            .appendingPathComponent(\"Library\")\n            .appendingPathComponent(\"WebKit\")\n            .appendingPathComponent(\"WebsiteData\")\n            .appendingPathComponent(\"Default\")\n\n        guard FileManager.default.fileExists(atPath: root.path) else { return [] }\n\n        let targets = [\"app.factory.ai\", \"auth.factory.ai\"]\n        var candidates: [LocalStorageCandidate] = []\n\n        guard let enumerator = FileManager.default.enumerator(\n            at: root,\n            includingPropertiesForKeys: [.isRegularFileKey],\n            options: [.skipsHiddenFiles])\n        else { return [] }\n\n        for case let fileURL as URL in enumerator {\n            guard fileURL.lastPathComponent == \"origin\" else { continue }\n            guard (try? fileURL.resourceValues(forKeys: [.isRegularFileKey]).isRegularFile) == true else {\n                continue\n            }\n            guard let data = try? Data(contentsOf: fileURL, options: [.mappedIfSafe]) else { continue }\n            let ascii = String(data: data, encoding: .utf8) ?? String(data: data, encoding: .isoLatin1) ?? \"\"\n            guard targets.contains(where: { ascii.contains($0) }) else { continue }\n\n            let storageURL = fileURL.deletingLastPathComponent()\n                .appendingPathComponent(\"LocalStorage\")\n                .appendingPathComponent(\"localstorage.sqlite3\")\n            guard FileManager.default.fileExists(atPath: storageURL.path) else { continue }\n            let host = self.extractSafariOriginHost(from: ascii) ?? \"app.factory.ai\"\n            candidates.append(LocalStorageCandidate(label: \"Safari (\\(host))\", kind: .safariSQLite(storageURL)))\n        }\n\n        var seen = Set<String>()\n        return candidates.filter { candidate in\n            let key: String = switch candidate.kind {\n            case let .chromeLevelDB(url):\n                url.path\n            case let .safariSQLite(url):\n                url.path\n            }\n            if seen.contains(key) { return false }\n            seen.insert(key)\n            return true\n        }\n    }\n\n    private static func extractSafariOriginHost(from ascii: String) -> String? {\n        let targets = [\"app.factory.ai\", \"auth.factory.ai\", \"factory.ai\"]\n        for host in targets where ascii.contains(host) {\n            return host\n        }\n        return nil\n    }\n\n    // MARK: - Token extraction\n\n    private struct WorkOSTokenMatch {\n        let refreshToken: String\n        let accessToken: String?\n        let organizationID: String?\n    }\n\n    private static func readWorkOSToken(from levelDBURL: URL) -> WorkOSTokenMatch? {\n        guard let entries = try? FileManager.default.contentsOfDirectory(\n            at: levelDBURL,\n            includingPropertiesForKeys: [.contentModificationDateKey],\n            options: [.skipsHiddenFiles])\n        else { return nil }\n\n        let files = entries.filter { url in\n            let ext = url.pathExtension.lowercased()\n            return ext == \"ldb\" || ext == \"log\"\n        }\n        .sorted { lhs, rhs in\n            let left = (try? lhs.resourceValues(forKeys: [.contentModificationDateKey]).contentModificationDate)\n            let right = (try? rhs.resourceValues(forKeys: [.contentModificationDateKey]).contentModificationDate)\n            return (left ?? .distantPast) > (right ?? .distantPast)\n        }\n\n        for file in files {\n            guard let data = try? Data(contentsOf: file, options: [.mappedIfSafe]) else { continue }\n            if let match = self.extractWorkOSToken(from: data) {\n                return match\n            }\n        }\n        return nil\n    }\n\n    private static func extractWorkOSToken(from data: Data) -> WorkOSTokenMatch? {\n        guard let contents = String(data: data, encoding: .utf8) ??\n            String(data: data, encoding: .isoLatin1)\n        else { return nil }\n        guard contents.contains(\"workos:refresh-token\") else { return nil }\n\n        let refreshToken = self.matchToken(\n            in: contents,\n            pattern: \"workos:refresh-token[^A-Za-z0-9_-]*([A-Za-z0-9_-]{20,})\")\n        guard let refreshToken else { return nil }\n\n        let accessToken = self.matchToken(\n            in: contents,\n            pattern: \"workos:access-token[^A-Za-z0-9_-]*([A-Za-z0-9_-]{20,})\")\n\n        let organizationID = self.extractOrganizationID(from: accessToken)\n        return WorkOSTokenMatch(\n            refreshToken: refreshToken,\n            accessToken: accessToken,\n            organizationID: organizationID)\n    }\n\n    private static func readWorkOSTokenFromSafariSQLite(\n        dbURL: URL,\n        logger: ((String) -> Void)? = nil) -> WorkOSTokenMatch?\n    {\n        var db: OpaquePointer?\n        guard sqlite3_open_v2(dbURL.path, &db, SQLITE_OPEN_READONLY, nil) == SQLITE_OK else {\n            if let c = sqlite3_errmsg(db) {\n                logger?(\"Safari local storage open failed: \\(String(cString: c))\")\n            }\n            return nil\n        }\n        defer { sqlite3_close(db) }\n\n        sqlite3_busy_timeout(db, 250)\n        let tables = self.fetchTableNames(db: db, logger: logger)\n        if tables.isEmpty {\n            logger?(\"Safari local storage table lookup returned no tables\")\n        }\n        let table = tables\n            .contains(\"ItemTable\") ? \"ItemTable\" : (tables.contains(\"localstorage\") ? \"localstorage\" : nil)\n        guard let table else {\n            logger?(\"Safari local storage missing ItemTable/localstorage tables (found: \\(tables.sorted()))\")\n            return nil\n        }\n\n        let refreshToken = self.fetchLocalStorageValue(db: db, table: table, key: \"workos:refresh-token\")\n        guard let refreshToken, !refreshToken.isEmpty else {\n            logger?(\"Safari local storage missing workos:refresh-token\")\n            return nil\n        }\n        let accessToken = self.fetchLocalStorageValue(db: db, table: table, key: \"workos:access-token\")\n\n        let organizationID = self.extractOrganizationID(from: accessToken)\n        return WorkOSTokenMatch(\n            refreshToken: refreshToken,\n            accessToken: accessToken,\n            organizationID: organizationID)\n    }\n\n    private static func extractOrganizationID(from accessToken: String?) -> String? {\n        guard let accessToken, accessToken.contains(\".\") else { return nil }\n        let parts = accessToken.split(separator: \".\")\n        guard parts.count == 3 else { return nil }\n        let payload = String(parts[1])\n            .replacingOccurrences(of: \"-\", with: \"+\")\n            .replacingOccurrences(of: \"_\", with: \"/\")\n        let padded = payload + String(repeating: \"=\", count: (4 - payload.count % 4) % 4)\n        guard let data = Data(base64Encoded: padded),\n              let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any]\n        else {\n            return nil\n        }\n        return json[\"org_id\"] as? String\n    }\n\n    private static func fetchTableNames(db: OpaquePointer?, logger: ((String) -> Void)? = nil) -> Set<String> {\n        let sql = \"SELECT name FROM sqlite_master WHERE type='table'\"\n        var stmt: OpaquePointer?\n        guard sqlite3_prepare_v2(db, sql, -1, &stmt, nil) == SQLITE_OK else {\n            if let c = sqlite3_errmsg(db) {\n                logger?(\"Safari local storage table query failed: \\(String(cString: c))\")\n            }\n            return []\n        }\n        defer { sqlite3_finalize(stmt) }\n        var names = Set<String>()\n        while true {\n            let step = sqlite3_step(stmt)\n            if step == SQLITE_ROW {\n                if let c = sqlite3_column_text(stmt, 0) {\n                    names.insert(String(cString: c))\n                }\n            } else {\n                if step != SQLITE_DONE, let c = sqlite3_errmsg(db) {\n                    logger?(\"Safari local storage table query failed: \\(String(cString: c))\")\n                }\n                break\n            }\n        }\n        return names\n    }\n\n    private static func fetchLocalStorageValue(db: OpaquePointer?, table: String, key: String) -> String? {\n        let sql = \"SELECT value FROM \\(table) WHERE key = ? LIMIT 1\"\n        var stmt: OpaquePointer?\n        guard sqlite3_prepare_v2(db, sql, -1, &stmt, nil) == SQLITE_OK else { return nil }\n        defer { sqlite3_finalize(stmt) }\n        let transient = unsafeBitCast(-1, to: sqlite3_destructor_type.self)\n        _ = key.withCString { cString in\n            sqlite3_bind_text(stmt, 1, cString, -1, transient)\n        }\n        guard sqlite3_step(stmt) == SQLITE_ROW else { return nil }\n        return self.decodeSQLiteValue(stmt: stmt, index: 0)\n    }\n\n    private static func decodeSQLiteValue(stmt: OpaquePointer?, index: Int32) -> String? {\n        let type = sqlite3_column_type(stmt, index)\n        switch type {\n        case SQLITE_TEXT:\n            guard let c = sqlite3_column_text(stmt, index) else { return nil }\n            return String(cString: c)\n        case SQLITE_BLOB:\n            guard let bytes = sqlite3_column_blob(stmt, index) else { return nil }\n            let count = Int(sqlite3_column_bytes(stmt, index))\n            let data = Data(bytes: bytes, count: count)\n            return self.decodeValueData(data)\n        default:\n            return nil\n        }\n    }\n\n    private static func decodeValueData(_ data: Data) -> String? {\n        if let decoded = String(data: data, encoding: .utf16LittleEndian) {\n            return decoded.trimmingCharacters(in: .controlCharacters)\n        }\n        if let decoded = String(data: data, encoding: .utf8) {\n            return decoded.trimmingCharacters(in: .controlCharacters)\n        }\n        if let decoded = String(data: data, encoding: .isoLatin1) {\n            return decoded.trimmingCharacters(in: .controlCharacters)\n        }\n        return nil\n    }\n\n    private static func matchToken(in contents: String, pattern: String) -> String? {\n        guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { return nil }\n        let range = NSRange(contents.startIndex..<contents.endIndex, in: contents)\n        guard let match = regex.matches(in: contents, options: [], range: range).last else { return nil }\n        guard match.numberOfRanges > 1,\n              let tokenRange = Range(match.range(at: 1), in: contents)\n        else { return nil }\n        return String(contents[tokenRange])\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Factory/FactoryProviderDescriptor.swift",
    "content": "import CodexBarMacroSupport\nimport Foundation\n\n@ProviderDescriptorRegistration\n@ProviderDescriptorDefinition\npublic enum FactoryProviderDescriptor {\n    static func makeDescriptor() -> ProviderDescriptor {\n        ProviderDescriptor(\n            id: .factory,\n            metadata: ProviderMetadata(\n                id: .factory,\n                displayName: \"Droid\",\n                sessionLabel: \"Standard\",\n                weeklyLabel: \"Premium\",\n                opusLabel: nil,\n                supportsOpus: false,\n                supportsCredits: false,\n                creditsHint: \"\",\n                toggleTitle: \"Show Droid usage\",\n                cliName: \"factory\",\n                defaultEnabled: false,\n                isPrimaryProvider: false,\n                usesAccountFallback: false,\n                browserCookieOrder: ProviderBrowserCookieDefaults.defaultImportOrder,\n                dashboardURL: \"https://app.factory.ai/settings/billing\",\n                statusPageURL: \"https://status.factory.ai\",\n                statusLinkURL: nil),\n            branding: ProviderBranding(\n                iconStyle: .factory,\n                iconResourceName: \"ProviderIcon-factory\",\n                color: ProviderColor(red: 255 / 255, green: 107 / 255, blue: 53 / 255)),\n            tokenCost: ProviderTokenCostConfig(\n                supportsTokenCost: false,\n                noDataMessage: { \"Droid cost summary is not supported.\" }),\n            fetchPlan: ProviderFetchPlan(\n                sourceModes: [.auto, .cli],\n                pipeline: ProviderFetchPipeline(resolveStrategies: { _ in [FactoryStatusFetchStrategy()] })),\n            cli: ProviderCLIConfig(\n                name: \"factory\",\n                versionDetector: nil))\n    }\n}\n\nstruct FactoryStatusFetchStrategy: ProviderFetchStrategy {\n    let id: String = \"factory.web\"\n    let kind: ProviderFetchKind = .web\n\n    func isAvailable(_ context: ProviderFetchContext) async -> Bool {\n        guard context.settings?.factory?.cookieSource != .off else { return false }\n        return true\n    }\n\n    func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {\n        let probe = FactoryStatusProbe(browserDetection: context.browserDetection)\n        let manual = Self.manualCookieHeader(from: context)\n        let snap = try await probe.fetch(cookieHeaderOverride: manual)\n        return self.makeResult(\n            usage: snap.toUsageSnapshot(),\n            sourceLabel: \"web\")\n    }\n\n    func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool {\n        false\n    }\n\n    private static func manualCookieHeader(from context: ProviderFetchContext) -> String? {\n        guard context.settings?.factory?.cookieSource == .manual else { return nil }\n        return CookieHeaderNormalizer.normalize(context.settings?.factory?.manualCookieHeader)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Factory/FactoryStatusProbe.swift",
    "content": "import Foundation\nimport SweetCookieKit\n\n#if os(macOS)\n\nprivate let factoryCookieImportOrder: BrowserCookieImportOrder =\n    ProviderDefaults.metadata[.factory]?.browserCookieOrder ?? Browser.defaultImportOrder\n\n// MARK: - Factory Cookie Importer\n\n/// Imports Factory session cookies from browser cookies.\npublic enum FactoryCookieImporter {\n    private static let cookieClient = BrowserCookieClient()\n    private static let sessionCookieNames: Set<String> = [\n        \"wos-session\",\n        \"__Secure-next-auth.session-token\",\n        \"next-auth.session-token\",\n        \"__Secure-authjs.session-token\",\n        \"__Host-authjs.csrf-token\",\n        \"authjs.session-token\",\n        \"session\",\n        \"access-token\",\n    ]\n\n    private static let authSessionCookieNames: Set<String> = [\n        \"__Secure-next-auth.session-token\",\n        \"next-auth.session-token\",\n        \"__Secure-authjs.session-token\",\n        \"authjs.session-token\",\n    ]\n    private static let appBaseURL = URL(string: \"https://app.factory.ai\")!\n    private static let authBaseURL = URL(string: \"https://auth.factory.ai\")!\n    private static let apiBaseURL = URL(string: \"https://api.factory.ai\")!\n\n    public struct SessionInfo: Sendable {\n        public let cookies: [HTTPCookie]\n        public let sourceLabel: String\n\n        public init(cookies: [HTTPCookie], sourceLabel: String) {\n            self.cookies = cookies\n            self.sourceLabel = sourceLabel\n        }\n\n        public var cookieHeader: String {\n            self.cookies.map { \"\\($0.name)=\\($0.value)\" }.joined(separator: \"; \")\n        }\n    }\n\n    /// Returns all Factory sessions across supported browsers.\n    public static func importSessions(\n        browserDetection: BrowserDetection,\n        logger: ((String) -> Void)? = nil) throws -> [SessionInfo]\n    {\n        let log: (String) -> Void = { msg in logger?(\"[factory-cookie] \\(msg)\") }\n        var sessions: [SessionInfo] = []\n\n        // Filter to cookie-eligible browsers to avoid unnecessary keychain prompts\n        let installedBrowsers = factoryCookieImportOrder.cookieImportCandidates(using: browserDetection)\n        for browserSource in installedBrowsers {\n            do {\n                let perSource = try self.importSessions(from: browserSource, logger: logger)\n                sessions.append(contentsOf: perSource)\n            } catch {\n                BrowserCookieAccessGate.recordIfNeeded(error)\n                log(\"\\(browserSource.displayName) cookie import failed: \\(error.localizedDescription)\")\n            }\n        }\n\n        guard !sessions.isEmpty else {\n            throw FactoryStatusProbeError.noSessionCookie\n        }\n        return sessions\n    }\n\n    public static func importSessions(\n        from browserSource: Browser,\n        logger: ((String) -> Void)? = nil) throws -> [SessionInfo]\n    {\n        let log: (String) -> Void = { msg in logger?(\"[factory-cookie] \\(msg)\") }\n        let cookieDomains = [\"factory.ai\", \"app.factory.ai\", \"auth.factory.ai\"]\n        let query = BrowserCookieQuery(domains: cookieDomains)\n        let sources = try Self.cookieClient.records(\n            matching: query,\n            in: browserSource,\n            logger: log)\n\n        var sessions: [SessionInfo] = []\n        for source in sources where !source.records.isEmpty {\n            let httpCookies = BrowserCookieClient.makeHTTPCookies(source.records, origin: query.origin)\n            if httpCookies.contains(where: { Self.sessionCookieNames.contains($0.name) }) {\n                log(\"Found \\(httpCookies.count) Factory cookies in \\(source.label)\")\n                log(\"\\(source.label) cookie names: \\(self.cookieNames(from: httpCookies))\")\n                if let token = httpCookies.first(where: { $0.name == \"access-token\" })?.value {\n                    let hint = token.contains(\".\") ? \"jwt\" : \"opaque\"\n                    log(\"\\(source.label) access-token cookie: \\(token.count) chars (\\(hint))\")\n                }\n                if let token = httpCookies.first(where: { self.authSessionCookieNames.contains($0.name) })?.value {\n                    let hint = token.contains(\".\") ? \"jwt\" : \"opaque\"\n                    log(\"\\(source.label) session cookie: \\(token.count) chars (\\(hint))\")\n                }\n                sessions.append(SessionInfo(cookies: httpCookies, sourceLabel: source.label))\n            } else {\n                log(\"\\(source.label) cookies found, but no Factory session cookie present\")\n            }\n        }\n        return sessions\n    }\n\n    /// Attempts to import Factory cookies using the standard browser import order.\n    public static func importSession(\n        browserDetection: BrowserDetection,\n        logger: ((String) -> Void)? = nil) throws -> SessionInfo\n    {\n        let sessions = try self.importSessions(browserDetection: browserDetection, logger: logger)\n        guard let first = sessions.first else {\n            throw FactoryStatusProbeError.noSessionCookie\n        }\n        return first\n    }\n\n    /// Check if Factory session cookies are available\n    public static func hasSession(browserDetection: BrowserDetection, logger: ((String) -> Void)? = nil) -> Bool {\n        do {\n            return try !(self.importSessions(browserDetection: browserDetection, logger: logger)).isEmpty\n        } catch {\n            return false\n        }\n    }\n\n    private static func cookieNames(from cookies: [HTTPCookie]) -> String {\n        let names = Set(cookies.map { \"\\($0.name)@\\($0.domain)\" }).sorted()\n        return names.joined(separator: \", \")\n    }\n}\n\n// MARK: - Factory API Models\n\npublic struct FactoryAuthResponse: Codable, Sendable {\n    public let featureFlags: FactoryFeatureFlags?\n    public let organization: FactoryOrganization?\n}\n\npublic struct FactoryFeatureFlags: Codable, Sendable {\n    public let flags: [String: Bool]?\n    public let configs: [String: AnyCodable]?\n}\n\npublic struct FactoryOrganization: Codable, Sendable {\n    public let id: String?\n    public let name: String?\n    public let subscription: FactorySubscription?\n\n    enum CodingKeys: String, CodingKey {\n        case id\n        case name\n        case subscription\n    }\n}\n\npublic struct FactorySubscription: Codable, Sendable {\n    public let factoryTier: String?\n    public let orbSubscription: FactoryOrbSubscription?\n}\n\npublic struct FactoryOrbSubscription: Codable, Sendable {\n    public let plan: FactoryPlan?\n    public let status: String?\n}\n\npublic struct FactoryPlan: Codable, Sendable {\n    public let name: String?\n    public let id: String?\n}\n\npublic struct FactoryUsageResponse: Codable, Sendable {\n    public let usage: FactoryUsageData?\n    public let source: String?\n    public let userId: String?\n}\n\npublic struct FactoryUsageData: Codable, Sendable {\n    public let startDate: Int64?\n    public let endDate: Int64?\n    public let standard: FactoryTokenUsage?\n    public let premium: FactoryTokenUsage?\n}\n\npublic struct FactoryTokenUsage: Codable, Sendable {\n    public let userTokens: Int64?\n    public let orgTotalTokensUsed: Int64?\n    public let totalAllowance: Int64?\n    public let usedRatio: Double?\n    public let orgOverageUsed: Int64?\n    public let basicAllowance: Int64?\n    public let orgOverageLimit: Int64?\n}\n\n/// Helper for encoding arbitrary JSON\npublic struct AnyCodable: Codable, Sendable {\n    public init(from decoder: Decoder) throws {\n        let container = try decoder.singleValueContainer()\n        if container.decodeNil() {\n            return\n        }\n        _ = try? container.decode([String: AnyCodable].self)\n    }\n\n    public func encode(to encoder: Encoder) throws {\n        var container = encoder.singleValueContainer()\n        try container.encodeNil()\n    }\n}\n\n// MARK: - Factory Status Snapshot\n\npublic struct FactoryStatusSnapshot: Sendable {\n    /// Standard token usage (user)\n    public let standardUserTokens: Int64\n    /// Standard token usage (org total)\n    public let standardOrgTokens: Int64\n    /// Standard token allowance\n    public let standardAllowance: Int64\n    /// Standard usage ratio from API (0.0-1.0), preferred over manual calculation\n    /// Falls back to percent-scale (0.0-100.0) when allowance is unavailable.\n    public let standardUsedRatio: Double?\n    /// Premium token usage (user)\n    public let premiumUserTokens: Int64\n    /// Premium token usage (org total)\n    public let premiumOrgTokens: Int64\n    /// Premium token allowance\n    public let premiumAllowance: Int64\n    /// Premium usage ratio from API (0.0-1.0), preferred over manual calculation\n    /// Falls back to percent-scale (0.0-100.0) when allowance is unavailable.\n    public let premiumUsedRatio: Double?\n    /// Billing period start\n    public let periodStart: Date?\n    /// Billing period end\n    public let periodEnd: Date?\n    /// Plan name\n    public let planName: String?\n    /// Factory tier (enterprise, team, etc.)\n    public let tier: String?\n    /// Organization name\n    public let organizationName: String?\n    /// User email\n    public let accountEmail: String?\n    /// User ID\n    public let userId: String?\n    /// Raw JSON for debugging\n    public let rawJSON: String?\n\n    public init(\n        standardUserTokens: Int64,\n        standardOrgTokens: Int64,\n        standardAllowance: Int64,\n        standardUsedRatio: Double? = nil,\n        premiumUserTokens: Int64,\n        premiumOrgTokens: Int64,\n        premiumAllowance: Int64,\n        premiumUsedRatio: Double? = nil,\n        periodStart: Date?,\n        periodEnd: Date?,\n        planName: String?,\n        tier: String?,\n        organizationName: String?,\n        accountEmail: String?,\n        userId: String?,\n        rawJSON: String?)\n    {\n        self.standardUserTokens = standardUserTokens\n        self.standardOrgTokens = standardOrgTokens\n        self.standardAllowance = standardAllowance\n        self.standardUsedRatio = standardUsedRatio\n        self.premiumUserTokens = premiumUserTokens\n        self.premiumOrgTokens = premiumOrgTokens\n        self.premiumAllowance = premiumAllowance\n        self.premiumUsedRatio = premiumUsedRatio\n        self.periodStart = periodStart\n        self.periodEnd = periodEnd\n        self.planName = planName\n        self.tier = tier\n        self.organizationName = organizationName\n        self.accountEmail = accountEmail\n        self.userId = userId\n        self.rawJSON = rawJSON\n    }\n\n    /// Convert to UsageSnapshot for the common provider interface\n    public func toUsageSnapshot() -> UsageSnapshot {\n        // Primary: Standard tokens used (as percentage of allowance, capped reasonably)\n        let standardPercent = self.calculateUsagePercent(\n            used: self.standardUserTokens,\n            allowance: self.standardAllowance,\n            apiRatio: self.standardUsedRatio)\n\n        let primary = RateWindow(\n            usedPercent: standardPercent,\n            windowMinutes: nil,\n            resetsAt: self.periodEnd,\n            resetDescription: self.periodEnd.map { Self.formatResetDate($0) })\n\n        // Secondary: Premium tokens used\n        let premiumPercent = self.calculateUsagePercent(\n            used: self.premiumUserTokens,\n            allowance: self.premiumAllowance,\n            apiRatio: self.premiumUsedRatio)\n\n        let secondary = RateWindow(\n            usedPercent: premiumPercent,\n            windowMinutes: nil,\n            resetsAt: self.periodEnd,\n            resetDescription: self.periodEnd.map { Self.formatResetDate($0) })\n\n        // Format login method as tier + plan\n        let loginMethod: String? = {\n            var parts: [String] = []\n            if let tier = self.tier, !tier.isEmpty {\n                parts.append(\"Factory \\(tier.capitalized)\")\n            }\n            if let plan = self.planName, !plan.isEmpty, !plan.lowercased().contains(\"factory\") {\n                parts.append(plan)\n            }\n            return parts.isEmpty ? nil : parts.joined(separator: \" - \")\n        }()\n\n        let identity = ProviderIdentitySnapshot(\n            providerID: .factory,\n            accountEmail: self.accountEmail,\n            accountOrganization: self.organizationName,\n            loginMethod: loginMethod)\n        return UsageSnapshot(\n            primary: primary,\n            secondary: secondary,\n            tertiary: nil,\n            providerCost: nil,\n            updatedAt: Date(),\n            identity: identity)\n    }\n\n    private func calculateUsagePercent(used: Int64, allowance: Int64, apiRatio: Double?) -> Double {\n        // Prefer API-provided ratio when available and valid.\n        // This handles plan-specific limits correctly on the server side,\n        // avoiding issues with missing/sentinel values in totalAllowance.\n        let unlimitedThreshold: Int64 = 1_000_000_000_000\n        if let ratio = apiRatio,\n           let percent = Self.percentFromAPIRatio(ratio, allowance: allowance, unlimitedThreshold: unlimitedThreshold)\n        {\n            return percent\n        }\n\n        // Fallback: calculate from used/allowance.\n        // Treat very large allowances (> 1 trillion) as unlimited.\n        if allowance > unlimitedThreshold {\n            // For unlimited, show a token count-based pseudo-percentage (capped at 100%).\n            // Use 100M tokens as a reference point for \"100%\".\n            let referenceTokens: Double = 100_000_000\n            return min(100, Double(used) / referenceTokens * 100)\n        }\n        guard allowance > 0 else { return 0 }\n        return min(100, Double(used) / Double(allowance) * 100)\n    }\n\n    private static func percentFromAPIRatio(\n        _ ratio: Double,\n        allowance: Int64,\n        unlimitedThreshold: Int64) -> Double?\n    {\n        guard ratio.isFinite else { return nil }\n\n        // Primary: ratio scale (0.0 - 1.0). Clamp to account for rounding.\n        if ratio >= -0.001, ratio <= 1.001 {\n            return min(100, max(0, ratio * 100))\n        }\n\n        // TODO: Confirm usedRatio contract (0.0-1.0 vs 0.0-100.0) and tighten this fallback.\n        // Secondary: percent scale (0.0 - 100.0), only when allowance is missing/unreliable.\n        // This avoids misinterpreting slightly-over-1 ratios when we can calculate locally.\n        let allowanceIsReliable = allowance > 0 && allowance <= unlimitedThreshold\n        if !allowanceIsReliable, ratio >= -0.1, ratio <= 100.1 {\n            return min(100, max(0, ratio))\n        }\n\n        return nil\n    }\n\n    private static func formatResetDate(_ date: Date) -> String {\n        let formatter = DateFormatter()\n        formatter.dateFormat = \"MMM d 'at' h:mma\"\n        formatter.locale = Locale(identifier: \"en_US_POSIX\")\n        return \"Resets \" + formatter.string(from: date)\n    }\n}\n\n// MARK: - Factory Status Probe Error\n\npublic enum FactoryStatusProbeError: LocalizedError, Sendable {\n    case notLoggedIn\n    case networkError(String)\n    case parseFailed(String)\n    case noSessionCookie\n\n    public var errorDescription: String? {\n        switch self {\n        case .notLoggedIn:\n            \"Not logged in to Factory. Please log in via the CodexBar menu.\"\n        case let .networkError(msg):\n            \"Factory API error: \\(msg)\"\n        case let .parseFailed(msg):\n            \"Could not parse Factory usage: \\(msg)\"\n        case .noSessionCookie:\n            \"No Factory session found. Please log in to app.factory.ai in \\(factoryCookieImportOrder.loginHint).\"\n        }\n    }\n}\n\n// MARK: - Factory Session Store\n\npublic actor FactorySessionStore {\n    public static let shared = FactorySessionStore()\n\n    private var sessionCookies: [HTTPCookie] = []\n    private var bearerToken: String?\n    private var refreshToken: String?\n    private let fileURL: URL\n    private var didLoadFromDisk = false\n\n    private init() {\n        let fm = FileManager.default\n        let appSupport = fm.urls(for: .applicationSupportDirectory, in: .userDomainMask).first\n            ?? fm.temporaryDirectory\n        let dir = appSupport.appendingPathComponent(\"CodexBar\", isDirectory: true)\n        try? fm.createDirectory(at: dir, withIntermediateDirectories: true)\n        self.fileURL = dir.appendingPathComponent(\"factory-session.json\")\n    }\n\n    public func setCookies(_ cookies: [HTTPCookie]) {\n        self.didLoadFromDisk = true\n        self.sessionCookies = cookies\n        self.saveToDisk()\n    }\n\n    public func getCookies() -> [HTTPCookie] {\n        self.loadFromDiskIfNeeded()\n        return self.sessionCookies\n    }\n\n    public func setBearerToken(_ token: String?) {\n        self.didLoadFromDisk = true\n        self.bearerToken = token\n        self.saveToDisk()\n    }\n\n    public func getBearerToken() -> String? {\n        self.loadFromDiskIfNeeded()\n        return self.bearerToken\n    }\n\n    public func setRefreshToken(_ token: String?) {\n        self.didLoadFromDisk = true\n        self.refreshToken = token\n        self.saveToDisk()\n    }\n\n    public func getRefreshToken() -> String? {\n        self.loadFromDiskIfNeeded()\n        return self.refreshToken\n    }\n\n    public func clearSession() {\n        self.didLoadFromDisk = true\n        self.sessionCookies = []\n        self.bearerToken = nil\n        self.refreshToken = nil\n        try? FileManager.default.removeItem(at: self.fileURL)\n    }\n\n    public func hasValidSession() -> Bool {\n        self.loadFromDiskIfNeeded()\n        return !self.sessionCookies.isEmpty || self.bearerToken != nil || self.refreshToken != nil\n    }\n\n    private func saveToDisk() {\n        let cookieData = self.sessionCookies.compactMap { cookie -> [String: Any]? in\n            guard let props = cookie.properties else { return nil }\n            var serializable: [String: Any] = [:]\n            for (key, value) in props {\n                let keyString = key.rawValue\n                if let date = value as? Date {\n                    serializable[keyString] = date.timeIntervalSince1970\n                    serializable[keyString + \"_isDate\"] = true\n                } else if let url = value as? URL {\n                    serializable[keyString] = url.absoluteString\n                    serializable[keyString + \"_isURL\"] = true\n                } else if JSONSerialization.isValidJSONObject([value]) ||\n                    value is String ||\n                    value is Bool ||\n                    value is NSNumber\n                {\n                    serializable[keyString] = value\n                }\n            }\n            return serializable\n        }\n\n        var payload: [String: Any] = [:]\n        if !cookieData.isEmpty {\n            payload[\"cookies\"] = cookieData\n        }\n        if let bearerToken {\n            payload[\"bearerToken\"] = bearerToken\n        }\n        if let refreshToken {\n            payload[\"refreshToken\"] = refreshToken\n        }\n\n        guard !payload.isEmpty,\n              let data = try? JSONSerialization.data(withJSONObject: payload, options: [.prettyPrinted])\n        else {\n            return\n        }\n        try? data.write(to: self.fileURL)\n    }\n\n    private func loadFromDisk() {\n        guard let data = try? Data(contentsOf: self.fileURL),\n              let json = try? JSONSerialization.jsonObject(with: data)\n        else { return }\n\n        var cookieArray: [[String: Any]] = []\n        if let dict = json as? [String: Any] {\n            if let stored = dict[\"cookies\"] as? [[String: Any]] {\n                cookieArray = stored\n            }\n            self.bearerToken = dict[\"bearerToken\"] as? String\n            self.refreshToken = dict[\"refreshToken\"] as? String\n        } else if let stored = json as? [[String: Any]] {\n            cookieArray = stored\n        }\n\n        self.sessionCookies = cookieArray.compactMap { props in\n            var cookieProps: [HTTPCookiePropertyKey: Any] = [:]\n            for (key, value) in props {\n                if key.hasSuffix(\"_isDate\") || key.hasSuffix(\"_isURL\") { continue }\n\n                let propKey = HTTPCookiePropertyKey(key)\n\n                if props[key + \"_isDate\"] as? Bool == true, let interval = value as? TimeInterval {\n                    cookieProps[propKey] = Date(timeIntervalSince1970: interval)\n                } else if props[key + \"_isURL\"] as? Bool == true, let urlString = value as? String {\n                    cookieProps[propKey] = URL(string: urlString)\n                } else {\n                    cookieProps[propKey] = value\n                }\n            }\n            return HTTPCookie(properties: cookieProps)\n        }\n    }\n\n    private func loadFromDiskIfNeeded() {\n        guard !self.didLoadFromDisk else { return }\n        self.didLoadFromDisk = true\n        self.loadFromDisk()\n    }\n}\n\n// MARK: - Factory Status Probe\n\npublic struct FactoryStatusProbe: Sendable {\n    public let baseURL: URL\n    public var timeout: TimeInterval = 15.0\n    private static let staleTokenCookieNames: Set<String> = [\n        \"access-token\",\n        \"__recent_auth\",\n    ]\n    private static let sessionCookieNames: Set<String> = [\n        \"session\",\n        \"wos-session\",\n    ]\n    private static let authSessionCookieNames: Set<String> = [\n        \"__Secure-next-auth.session-token\",\n        \"next-auth.session-token\",\n        \"__Secure-authjs.session-token\",\n        \"authjs.session-token\",\n    ]\n    static let appBaseURL = URL(string: \"https://app.factory.ai\")!\n    static let authBaseURL = URL(string: \"https://auth.factory.ai\")!\n    static let apiBaseURL = URL(string: \"https://api.factory.ai\")!\n    private static let workosClientIDs = [\n        \"client_01HXRMBQ9BJ3E7QSTQ9X2PHVB7\",\n        \"client_01HNM792M5G5G1A2THWPXKFMXB\",\n    ]\n\n    private struct WorkOSAuthResponse: Decodable {\n        let access_token: String\n        let refresh_token: String?\n        let organization_id: String?\n    }\n\n    private let browserDetection: BrowserDetection\n\n    public init(\n        baseURL: URL = URL(string: \"https://app.factory.ai\")!,\n        timeout: TimeInterval = 15.0,\n        browserDetection: BrowserDetection)\n    {\n        self.baseURL = baseURL\n        self.timeout = timeout\n        self.browserDetection = browserDetection\n    }\n\n    /// Fetch Factory usage using browser cookies with fallback to stored session.\n    public func fetch(\n        cookieHeaderOverride: String? = nil,\n        logger: ((String) -> Void)? = nil) async throws -> FactoryStatusSnapshot\n    {\n        let log: (String) -> Void = { msg in logger?(\"[factory] \\(msg)\") }\n        var lastError: Error?\n\n        if let override = CookieHeaderNormalizer.normalize(cookieHeaderOverride) {\n            log(\"Using manual cookie header\")\n            let bearer = Self.bearerToken(fromHeader: override)\n            let candidates = [\n                self.baseURL,\n                Self.authBaseURL,\n                Self.apiBaseURL,\n            ]\n            for baseURL in candidates {\n                do {\n                    return try await self.fetchWithCookieHeader(\n                        override,\n                        bearerToken: bearer,\n                        baseURL: baseURL)\n                } catch {\n                    lastError = error\n                }\n            }\n            if let lastError { throw lastError }\n            throw FactoryStatusProbeError.noSessionCookie\n        }\n\n        if let cached = CookieHeaderCache.load(provider: .factory),\n           !cached.cookieHeader.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty\n        {\n            log(\"Using cached cookie header from \\(cached.sourceLabel)\")\n            let bearer = Self.bearerToken(fromHeader: cached.cookieHeader)\n            do {\n                return try await self.fetchWithCookieHeader(\n                    cached.cookieHeader,\n                    bearerToken: bearer,\n                    baseURL: self.baseURL)\n            } catch {\n                if case FactoryStatusProbeError.notLoggedIn = error {\n                    CookieHeaderCache.clear(provider: .factory)\n                }\n                lastError = error\n            }\n        }\n\n        // Filter to only installed browsers to avoid unnecessary keychain prompts\n        let installedChromiumAndFirefox = [.chrome, .firefox].cookieImportCandidates(using: self.browserDetection)\n\n        let attempts: [FetchAttemptResult] = await [\n            self.attemptStoredCookies(logger: log),\n            self.attemptStoredBearer(logger: log),\n            self.attemptStoredRefreshToken(logger: log),\n            self.attemptLocalStorageTokens(logger: log),\n            self.attemptBrowserCookies(logger: log, sources: [.safari]),\n            self.attemptWorkOSCookies(logger: log, sources: [.safari]),\n            self.attemptBrowserCookies(logger: log, sources: installedChromiumAndFirefox),\n            self.attemptWorkOSCookies(logger: log, sources: installedChromiumAndFirefox),\n        ]\n\n        for result in attempts {\n            switch result {\n            case let .success(snapshot):\n                return snapshot\n            case let .failure(error):\n                lastError = error\n            case .skipped:\n                continue\n            }\n        }\n\n        if let lastError { throw lastError }\n        throw FactoryStatusProbeError.noSessionCookie\n    }\n\n    private enum FetchAttemptResult {\n        case success(FactoryStatusSnapshot)\n        case failure(Error)\n        case skipped\n    }\n\n    private func attemptBrowserCookies(\n        logger: @escaping (String) -> Void,\n        sources: [Browser]) async -> FetchAttemptResult\n    {\n        do {\n            var lastError: Error?\n            for browserSource in sources {\n                let sessions = try FactoryCookieImporter.importSessions(from: browserSource, logger: logger)\n                for session in sessions {\n                    logger(\"Using cookies from \\(session.sourceLabel)\")\n                    do {\n                        let snapshot = try await self.fetchWithCookies(session.cookies, logger: logger)\n                        await FactorySessionStore.shared.setCookies(session.cookies)\n                        CookieHeaderCache.store(\n                            provider: .factory,\n                            cookieHeader: session.cookieHeader,\n                            sourceLabel: session.sourceLabel)\n                        return .success(snapshot)\n                    } catch {\n                        lastError = error\n                        logger(\"Browser session fetch failed for \\(session.sourceLabel): \\(error.localizedDescription)\")\n                    }\n                }\n            }\n            if let lastError { return .failure(lastError) }\n            return .skipped\n        } catch {\n            BrowserCookieAccessGate.recordIfNeeded(error)\n            logger(\"Browser cookie import failed: \\(error.localizedDescription)\")\n            return .failure(error)\n        }\n    }\n\n    private func attemptStoredCookies(logger: (String) -> Void) async -> FetchAttemptResult {\n        let storedCookies = await FactorySessionStore.shared.getCookies()\n        guard !storedCookies.isEmpty else { return .skipped }\n        logger(\"Using stored session cookies\")\n        do {\n            return try await .success(self.fetchWithCookies(storedCookies, logger: logger))\n        } catch {\n            if case FactoryStatusProbeError.notLoggedIn = error {\n                await FactorySessionStore.shared.clearSession()\n                logger(\"Stored session invalid, cleared\")\n            } else {\n                logger(\"Stored session failed: \\(error.localizedDescription)\")\n            }\n            return .failure(error)\n        }\n    }\n\n    private func attemptStoredBearer(logger: (String) -> Void) async -> FetchAttemptResult {\n        guard let bearerToken = await FactorySessionStore.shared.getBearerToken() else { return .skipped }\n        logger(\"Using stored Factory bearer token\")\n        do {\n            return try await .success(self.fetchWithBearerToken(bearerToken, logger: logger))\n        } catch {\n            return .failure(error)\n        }\n    }\n\n    private func attemptStoredRefreshToken(logger: (String) -> Void) async -> FetchAttemptResult {\n        guard let refreshToken = await FactorySessionStore.shared.getRefreshToken(),\n              !refreshToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty\n        else {\n            return .skipped\n        }\n        logger(\"Using stored WorkOS refresh token\")\n        do {\n            return try await .success(self.fetchWithWorkOSRefreshToken(\n                refreshToken,\n                organizationID: nil,\n                logger: logger))\n        } catch {\n            if self.isInvalidGrant(error) {\n                await FactorySessionStore.shared.setRefreshToken(nil)\n            } else if case FactoryStatusProbeError.noSessionCookie = error {\n                await FactorySessionStore.shared.setRefreshToken(nil)\n            }\n            return .failure(error)\n        }\n    }\n\n    private func attemptLocalStorageTokens(logger: @escaping (String) -> Void) async -> FetchAttemptResult {\n        let workosTokens = FactoryLocalStorageImporter.importWorkOSTokens(\n            browserDetection: self.browserDetection,\n            logger: logger)\n        guard !workosTokens.isEmpty else { return .skipped }\n        var lastError: Error?\n        for token in workosTokens {\n            guard !token.refreshToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {\n                continue\n            }\n            logger(\"Using WorkOS refresh token from \\(token.sourceLabel)\")\n            if let accessToken = token.accessToken {\n                do {\n                    await FactorySessionStore.shared.setBearerToken(accessToken)\n                    return try await .success(self.fetchWithBearerToken(accessToken, logger: logger))\n                } catch {\n                    lastError = error\n                }\n            }\n            do {\n                return try await .success(self.fetchWithWorkOSRefreshToken(\n                    token.refreshToken,\n                    organizationID: token.organizationID,\n                    logger: logger))\n            } catch {\n                if self.isInvalidGrant(error) {\n                    await FactorySessionStore.shared.setRefreshToken(nil)\n                }\n                lastError = error\n            }\n        }\n        if let lastError { return .failure(lastError) }\n        return .skipped\n    }\n\n    private func attemptWorkOSCookies(\n        logger: @escaping (String) -> Void,\n        sources: [Browser]) async -> FetchAttemptResult\n    {\n        let log: (String) -> Void = { msg in logger(\"[factory-workos] \\(msg)\") }\n        var lastError: Error?\n\n        for browserSource in sources {\n            do {\n                let query = BrowserCookieQuery(domains: [\"workos.com\"])\n                let sources = try BrowserCookieClient().records(\n                    matching: query,\n                    in: browserSource,\n                    logger: log)\n                for source in sources where !source.records.isEmpty {\n                    let cookies = BrowserCookieClient.makeHTTPCookies(source.records, origin: query.origin)\n                    guard !cookies.isEmpty else { continue }\n                    log(\"Using WorkOS cookies from \\(source.label)\")\n                    do {\n                        let auth = try await self.fetchWorkOSAccessTokenWithCookies(\n                            cookies: cookies,\n                            logger: logger)\n                        await FactorySessionStore.shared.setBearerToken(auth.access_token)\n                        if let refreshToken = auth.refresh_token {\n                            await FactorySessionStore.shared.setRefreshToken(refreshToken)\n                        }\n                        return try await .success(self.fetchWithBearerToken(auth.access_token, logger: logger))\n                    } catch {\n                        lastError = error\n                        log(\"WorkOS cookie auth failed for \\(source.label): \\(error.localizedDescription)\")\n                    }\n                }\n            } catch {\n                BrowserCookieAccessGate.recordIfNeeded(error)\n                log(\"\\(browserSource.displayName) WorkOS cookie import failed: \\(error.localizedDescription)\")\n                lastError = error\n            }\n        }\n\n        if let lastError { return .failure(lastError) }\n        return .skipped\n    }\n\n    private func fetchWithWorkOSRefreshToken(\n        _ refreshToken: String,\n        organizationID: String?,\n        logger: (String) -> Void) async throws -> FactoryStatusSnapshot\n    {\n        let auth = try await self.fetchWorkOSAccessToken(\n            refreshToken: refreshToken,\n            organizationID: organizationID)\n        await FactorySessionStore.shared.setBearerToken(auth.access_token)\n        if let newRefresh = auth.refresh_token {\n            await FactorySessionStore.shared.setRefreshToken(newRefresh)\n        }\n        return try await self.fetchWithBearerToken(auth.access_token, logger: logger)\n    }\n\n    private func fetchWithCookies(\n        _ cookies: [HTTPCookie],\n        logger: (String) -> Void) async throws -> FactoryStatusSnapshot\n    {\n        let candidates = Self.baseURLCandidates(default: self.baseURL, cookies: cookies)\n        var lastError: Error?\n\n        for baseURL in candidates {\n            if baseURL != self.baseURL {\n                logger(\"Trying Factory base URL: \\(baseURL.host ?? baseURL.absoluteString)\")\n            }\n            do {\n                return try await self.fetchWithCookies(cookies, baseURL: baseURL, logger: logger)\n            } catch {\n                lastError = error\n            }\n        }\n\n        if let lastError { throw lastError }\n        throw FactoryStatusProbeError.noSessionCookie\n    }\n\n    private func fetchWithCookies(\n        _ cookies: [HTTPCookie],\n        baseURL: URL,\n        logger: (String) -> Void) async throws -> FactoryStatusSnapshot\n    {\n        let header = Self.cookieHeader(from: cookies)\n        let bearerToken = Self.bearerToken(from: cookies)\n        do {\n            return try await self.fetchWithCookieHeader(header, bearerToken: bearerToken, baseURL: baseURL)\n        } catch let error as FactoryStatusProbeError {\n            if case .notLoggedIn = error, bearerToken != nil {\n                logger(\"Retrying without Authorization header\")\n                return try await self.fetchWithCookieHeader(header, bearerToken: nil, baseURL: baseURL)\n            }\n            guard case let .networkError(message) = error,\n                  message.contains(\"HTTP 409\")\n            else {\n                throw error\n            }\n\n            var lastError: Error? = error\n            if bearerToken != nil {\n                logger(\"Retrying without Authorization header (HTTP 409)\")\n                do {\n                    return try await self.fetchWithCookieHeader(header, bearerToken: nil, baseURL: baseURL)\n                } catch {\n                    lastError = error\n                }\n            }\n\n            let retries: [(String, (HTTPCookie) -> Bool)] = [\n                (\"Retrying without access-token cookies\", { !Self.staleTokenCookieNames.contains($0.name) }),\n                (\"Retrying without session cookies\", { !Self.sessionCookieNames.contains($0.name) }),\n                (\"Retrying without access-token/session cookies\", {\n                    !Self.staleTokenCookieNames.contains($0.name) && !Self.sessionCookieNames.contains($0.name)\n                }),\n            ]\n\n            for (label, predicate) in retries {\n                let filtered = cookies.filter(predicate)\n                guard filtered.count < cookies.count else { continue }\n                logger(label)\n                do {\n                    let filteredBearer = Self.bearerToken(from: filtered)\n                    return try await self.fetchWithCookieHeader(\n                        Self.cookieHeader(from: filtered),\n                        bearerToken: filteredBearer,\n                        baseURL: baseURL)\n                } catch let retryError as FactoryStatusProbeError {\n                    switch retryError {\n                    case let .networkError(retryMessage)\n                        where retryMessage.contains(\"HTTP 409\") &&\n                        retryMessage.localizedCaseInsensitiveContains(\"stale token\"):\n                        lastError = retryError\n                        continue\n                    case .notLoggedIn:\n                        lastError = retryError\n                        continue\n                    default:\n                        throw retryError\n                    }\n                }\n            }\n\n            let authOnly = cookies.filter {\n                Self.authSessionCookieNames.contains($0.name) || $0.name == \"__Host-authjs.csrf-token\"\n            }\n            if !authOnly.isEmpty, authOnly.count < cookies.count {\n                logger(\"Retrying with auth session cookies only\")\n                do {\n                    return try await self.fetchWithCookieHeader(\n                        Self.cookieHeader(from: authOnly),\n                        bearerToken: Self.bearerToken(from: authOnly),\n                        baseURL: baseURL)\n                } catch let retryError as FactoryStatusProbeError {\n                    lastError = retryError\n                }\n            }\n\n            if let lastError { throw lastError }\n            throw error\n        } catch {\n            throw error\n        }\n    }\n\n    private static func cookieHeader(from cookies: [HTTPCookie]) -> String {\n        cookies.map { \"\\($0.name)=\\($0.value)\" }.joined(separator: \"; \")\n    }\n\n    private static func bearerToken(fromHeader cookieHeader: String) -> String? {\n        for pair in CookieHeaderNormalizer.pairs(from: cookieHeader) where pair.name == \"access-token\" {\n            let token = pair.value.trimmingCharacters(in: .whitespacesAndNewlines)\n            if !token.isEmpty { return token }\n        }\n        return nil\n    }\n\n    private func fetchWithCookieHeader(\n        _ cookieHeader: String,\n        bearerToken: String?,\n        baseURL: URL) async throws -> FactoryStatusSnapshot\n    {\n        // First fetch auth info to get user ID and org info\n        let authInfo = try await self.fetchAuthInfo(\n            cookieHeader: cookieHeader,\n            bearerToken: bearerToken,\n            baseURL: baseURL)\n\n        // Extract user ID from JWT in the auth response or use a default endpoint\n        let userId = self.extractUserIdFromAuth(authInfo)\n\n        // Fetch usage data\n        let usageData = try await self.fetchUsage(\n            cookieHeader: cookieHeader,\n            bearerToken: bearerToken,\n            userId: userId,\n            baseURL: baseURL)\n\n        return self.buildSnapshot(authInfo: authInfo, usageData: usageData, userId: userId)\n    }\n\n    private func fetchAuthInfo(\n        cookieHeader: String,\n        bearerToken: String?,\n        baseURL: URL) async throws -> FactoryAuthResponse\n    {\n        let url = baseURL.appendingPathComponent(\"/api/app/auth/me\")\n        var request = URLRequest(url: url)\n        request.timeoutInterval = self.timeout\n        request.httpMethod = \"GET\"\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Accept\")\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Content-Type\")\n        request.setValue(\"https://app.factory.ai\", forHTTPHeaderField: \"Origin\")\n        request.setValue(\"https://app.factory.ai/\", forHTTPHeaderField: \"Referer\")\n        if !cookieHeader.isEmpty {\n            request.setValue(cookieHeader, forHTTPHeaderField: \"Cookie\")\n        }\n        request.setValue(\"web-app\", forHTTPHeaderField: \"x-factory-client\")\n        if let bearerToken {\n            request.setValue(\"Bearer \\(bearerToken)\", forHTTPHeaderField: \"Authorization\")\n        }\n\n        let (data, response) = try await URLSession.shared.data(for: request)\n\n        guard let httpResponse = response as? HTTPURLResponse else {\n            throw FactoryStatusProbeError.networkError(\"Invalid response\")\n        }\n\n        if httpResponse.statusCode == 401 || httpResponse.statusCode == 403 {\n            throw FactoryStatusProbeError.notLoggedIn\n        }\n\n        guard httpResponse.statusCode == 200 else {\n            let body = String(data: data, encoding: .utf8)?\n                .trimmingCharacters(in: .whitespacesAndNewlines) ?? \"<binary>\"\n            let snippet = body.isEmpty ? \"\" : \": \\(body.prefix(200))\"\n            throw FactoryStatusProbeError.networkError(\"HTTP \\(httpResponse.statusCode)\\(snippet)\")\n        }\n\n        do {\n            let decoder = JSONDecoder()\n            return try decoder.decode(FactoryAuthResponse.self, from: data)\n        } catch {\n            let rawJSON = String(data: data, encoding: .utf8) ?? \"<binary>\"\n            throw FactoryStatusProbeError\n                .parseFailed(\"Auth decode failed: \\(error.localizedDescription). Raw: \\(rawJSON.prefix(200))\")\n        }\n    }\n\n    private func fetchUsage(\n        cookieHeader: String,\n        bearerToken: String?,\n        userId: String?,\n        baseURL: URL) async throws -> FactoryUsageResponse\n    {\n        let url = baseURL.appendingPathComponent(\"/api/organization/subscription/usage\")\n        var request = URLRequest(url: url)\n        request.timeoutInterval = self.timeout\n        request.httpMethod = \"POST\"\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Accept\")\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Content-Type\")\n        request.setValue(\"https://app.factory.ai\", forHTTPHeaderField: \"Origin\")\n        request.setValue(\"https://app.factory.ai/\", forHTTPHeaderField: \"Referer\")\n        if !cookieHeader.isEmpty {\n            request.setValue(cookieHeader, forHTTPHeaderField: \"Cookie\")\n        }\n        request.setValue(\"web-app\", forHTTPHeaderField: \"x-factory-client\")\n        if let bearerToken {\n            request.setValue(\"Bearer \\(bearerToken)\", forHTTPHeaderField: \"Authorization\")\n        }\n\n        // Build request body\n        var body: [String: Any] = [\"useCache\": true]\n        if let userId {\n            body[\"userId\"] = userId\n        }\n        request.httpBody = try? JSONSerialization.data(withJSONObject: body)\n\n        let (data, response) = try await URLSession.shared.data(for: request)\n\n        guard let httpResponse = response as? HTTPURLResponse else {\n            throw FactoryStatusProbeError.networkError(\"Invalid response\")\n        }\n\n        if httpResponse.statusCode == 401 || httpResponse.statusCode == 403 {\n            throw FactoryStatusProbeError.notLoggedIn\n        }\n\n        guard httpResponse.statusCode == 200 else {\n            let body = String(data: data, encoding: .utf8)?\n                .trimmingCharacters(in: .whitespacesAndNewlines) ?? \"<binary>\"\n            let snippet = body.isEmpty ? \"\" : \": \\(body.prefix(200))\"\n            throw FactoryStatusProbeError.networkError(\"HTTP \\(httpResponse.statusCode)\\(snippet)\")\n        }\n\n        do {\n            let decoder = JSONDecoder()\n            return try decoder.decode(FactoryUsageResponse.self, from: data)\n        } catch {\n            let rawJSON = String(data: data, encoding: .utf8) ?? \"<binary>\"\n            throw FactoryStatusProbeError\n                .parseFailed(\"Usage decode failed: \\(error.localizedDescription). Raw: \\(rawJSON.prefix(200))\")\n        }\n    }\n\n    private static func baseURLCandidates(default baseURL: URL, cookies: [HTTPCookie]) -> [URL] {\n        let cookieDomains = Set(\n            cookies.map {\n                $0.domain.trimmingCharacters(in: CharacterSet(charactersIn: \".\"))\n            })\n\n        var candidates: [URL] = []\n        if cookieDomains.contains(\"auth.factory.ai\") {\n            candidates.append(Self.authBaseURL)\n        }\n        candidates.append(Self.apiBaseURL)\n        candidates.append(Self.appBaseURL)\n        candidates.append(baseURL)\n\n        var seen = Set<String>()\n        return candidates.filter { url in\n            let key = url.absoluteString\n            if seen.contains(key) { return false }\n            seen.insert(key)\n            return true\n        }\n    }\n\n    private static func bearerToken(from cookies: [HTTPCookie]) -> String? {\n        let accessToken = cookies.first(where: { $0.name == \"access-token\" })?.value\n        let sessionToken = cookies.first(where: { Self.authSessionCookieNames.contains($0.name) })?.value\n        let legacySession = cookies.first(where: { $0.name == \"session\" })?.value\n\n        if let accessToken, accessToken.contains(\".\") {\n            return accessToken\n        }\n        if let sessionToken, sessionToken.contains(\".\") {\n            return sessionToken\n        }\n        if let legacySession, legacySession.contains(\".\") {\n            return legacySession\n        }\n        return accessToken ?? sessionToken\n    }\n\n    private func fetchWithBearerToken(\n        _ bearerToken: String,\n        logger: (String) -> Void) async throws -> FactoryStatusSnapshot\n    {\n        let candidates = [Self.apiBaseURL, self.baseURL]\n        var lastError: Error?\n        for baseURL in candidates {\n            if baseURL != Self.apiBaseURL {\n                logger(\"Trying Factory bearer base URL: \\(baseURL.host ?? baseURL.absoluteString)\")\n            }\n            do {\n                return try await self.fetchWithCookieHeader(\n                    \"\",\n                    bearerToken: bearerToken,\n                    baseURL: baseURL)\n            } catch {\n                lastError = error\n            }\n        }\n        if let lastError { throw lastError }\n        throw FactoryStatusProbeError.notLoggedIn\n    }\n\n    private func fetchWorkOSAccessToken(\n        refreshToken: String,\n        organizationID: String?) async throws -> WorkOSAuthResponse\n    {\n        var lastError: Error?\n        for clientID in Self.workosClientIDs {\n            do {\n                return try await self.fetchWorkOSAccessToken(\n                    refreshToken: refreshToken,\n                    organizationID: organizationID,\n                    clientID: clientID)\n            } catch {\n                lastError = error\n            }\n        }\n        if let lastError { throw lastError }\n        throw FactoryStatusProbeError.networkError(\"WorkOS auth failed\")\n    }\n\n    private func fetchWorkOSAccessToken(\n        refreshToken: String,\n        organizationID: String?,\n        clientID: String) async throws -> WorkOSAuthResponse\n    {\n        guard let url = URL(string: \"https://api.workos.com/user_management/authenticate\") else {\n            throw FactoryStatusProbeError.networkError(\"WorkOS auth URL unavailable\")\n        }\n\n        var request = URLRequest(url: url)\n        request.timeoutInterval = self.timeout\n        request.httpMethod = \"POST\"\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Accept\")\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Content-Type\")\n\n        var body: [String: Any] = [\n            \"client_id\": clientID,\n            \"grant_type\": \"refresh_token\",\n            \"refresh_token\": refreshToken,\n        ]\n        if let organizationID {\n            body[\"organization_id\"] = organizationID\n        }\n        request.httpBody = try JSONSerialization.data(withJSONObject: body, options: [])\n\n        let (data, response) = try await URLSession.shared.data(for: request)\n        guard let httpResponse = response as? HTTPURLResponse else {\n            throw FactoryStatusProbeError.networkError(\"Invalid WorkOS response\")\n        }\n\n        guard httpResponse.statusCode == 200 else {\n            if httpResponse.statusCode == 400, Self.isMissingWorkOSRefreshToken(data) {\n                throw FactoryStatusProbeError.noSessionCookie\n            }\n            let body = String(data: data, encoding: .utf8)?\n                .trimmingCharacters(in: .whitespacesAndNewlines) ?? \"<binary>\"\n            let snippet = body.isEmpty ? \"\" : \": \\(body.prefix(200))\"\n            throw FactoryStatusProbeError.networkError(\"WorkOS HTTP \\(httpResponse.statusCode)\\(snippet)\")\n        }\n\n        let decoder = JSONDecoder()\n        return try decoder.decode(WorkOSAuthResponse.self, from: data)\n    }\n\n    private func fetchWorkOSAccessTokenWithCookies(\n        cookies: [HTTPCookie],\n        logger: (String) -> Void) async throws -> WorkOSAuthResponse\n    {\n        let cookieHeader = Self.cookieHeader(from: cookies)\n        guard !cookieHeader.isEmpty else {\n            throw FactoryStatusProbeError.networkError(\"Missing WorkOS cookies\")\n        }\n\n        var lastError: Error?\n        for clientID in Self.workosClientIDs {\n            do {\n                return try await self.fetchWorkOSAccessTokenWithCookies(\n                    cookieHeader: cookieHeader,\n                    organizationID: nil,\n                    clientID: clientID)\n            } catch {\n                lastError = error\n                logger(\"WorkOS cookie auth failed for client \\(clientID): \\(error.localizedDescription)\")\n            }\n        }\n        if let lastError { throw lastError }\n        throw FactoryStatusProbeError.networkError(\"WorkOS cookie auth failed\")\n    }\n\n    private func fetchWorkOSAccessTokenWithCookies(\n        cookieHeader: String,\n        organizationID: String?,\n        clientID: String) async throws -> WorkOSAuthResponse\n    {\n        guard let url = URL(string: \"https://api.workos.com/user_management/authenticate\") else {\n            throw FactoryStatusProbeError.networkError(\"WorkOS auth URL unavailable\")\n        }\n\n        var request = URLRequest(url: url)\n        request.timeoutInterval = self.timeout\n        request.httpMethod = \"POST\"\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Accept\")\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Content-Type\")\n        request.setValue(cookieHeader, forHTTPHeaderField: \"Cookie\")\n\n        var body: [String: Any] = [\n            \"client_id\": clientID,\n            \"grant_type\": \"refresh_token\",\n            \"useCookie\": true,\n        ]\n        if let organizationID {\n            body[\"organization_id\"] = organizationID\n        }\n        request.httpBody = try JSONSerialization.data(withJSONObject: body, options: [])\n\n        let (data, response) = try await URLSession.shared.data(for: request)\n        guard let httpResponse = response as? HTTPURLResponse else {\n            throw FactoryStatusProbeError.networkError(\"Invalid WorkOS response\")\n        }\n\n        guard httpResponse.statusCode == 200 else {\n            if httpResponse.statusCode == 400, Self.isMissingWorkOSRefreshToken(data) {\n                throw FactoryStatusProbeError.noSessionCookie\n            }\n            let bodyText = String(data: data, encoding: .utf8)?\n                .trimmingCharacters(in: .whitespacesAndNewlines) ?? \"<binary>\"\n            let snippet = bodyText.isEmpty ? \"\" : \": \\(bodyText.prefix(200))\"\n            throw FactoryStatusProbeError.networkError(\"WorkOS HTTP \\(httpResponse.statusCode)\\(snippet)\")\n        }\n\n        let decoder = JSONDecoder()\n        return try decoder.decode(WorkOSAuthResponse.self, from: data)\n    }\n\n    private func isInvalidGrant(_ error: Error) -> Bool {\n        guard case let FactoryStatusProbeError.networkError(message) = error else {\n            return false\n        }\n        return message.localizedCaseInsensitiveContains(\"invalid_grant\")\n    }\n\n    static func isMissingWorkOSRefreshToken(_ data: Data) -> Bool {\n        guard let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else {\n            return false\n        }\n        guard let description = json[\"error_description\"] as? String else { return false }\n        return description.localizedCaseInsensitiveContains(\"missing refresh token\")\n    }\n\n    private func extractUserIdFromAuth(_ auth: FactoryAuthResponse) -> String? {\n        // The user ID might be in the organization or we might need to parse JWT\n        // For now, return nil and let the API handle it\n        nil\n    }\n\n    private func buildSnapshot(\n        authInfo: FactoryAuthResponse,\n        usageData: FactoryUsageResponse,\n        userId: String?) -> FactoryStatusSnapshot\n    {\n        let usage = usageData.usage\n\n        let periodStart: Date? = usage?.startDate.map { Date(timeIntervalSince1970: TimeInterval($0) / 1000) }\n        let periodEnd: Date? = usage?.endDate.map { Date(timeIntervalSince1970: TimeInterval($0) / 1000) }\n\n        return FactoryStatusSnapshot(\n            standardUserTokens: usage?.standard?.userTokens ?? 0,\n            standardOrgTokens: usage?.standard?.orgTotalTokensUsed ?? 0,\n            standardAllowance: usage?.standard?.totalAllowance ?? 0,\n            standardUsedRatio: usage?.standard?.usedRatio,\n            premiumUserTokens: usage?.premium?.userTokens ?? 0,\n            premiumOrgTokens: usage?.premium?.orgTotalTokensUsed ?? 0,\n            premiumAllowance: usage?.premium?.totalAllowance ?? 0,\n            premiumUsedRatio: usage?.premium?.usedRatio,\n            periodStart: periodStart,\n            periodEnd: periodEnd,\n            planName: authInfo.organization?.subscription?.orbSubscription?.plan?.name,\n            tier: authInfo.organization?.subscription?.factoryTier,\n            organizationName: authInfo.organization?.name,\n            accountEmail: nil, // Email is in JWT, not in auth response body\n            userId: userId ?? usageData.userId,\n            rawJSON: nil)\n    }\n}\n\n#else\n\n// MARK: - Factory (Unsupported)\n\npublic enum FactoryStatusProbeError: LocalizedError, Sendable {\n    case notSupported\n\n    public var errorDescription: String? {\n        \"Factory is only supported on macOS.\"\n    }\n}\n\npublic struct FactoryStatusSnapshot: Sendable {\n    public init() {}\n\n    public func toUsageSnapshot() -> UsageSnapshot {\n        UsageSnapshot(\n            primary: RateWindow(usedPercent: 0, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            secondary: nil,\n            tertiary: nil,\n            providerCost: nil,\n            updatedAt: Date(),\n            identity: nil)\n    }\n}\n\npublic struct FactoryStatusProbe: Sendable {\n    public init(\n        baseURL: URL = URL(string: \"https://app.factory.ai\")!,\n        timeout: TimeInterval = 15.0,\n        browserDetection: BrowserDetection)\n    {\n        _ = baseURL\n        _ = timeout\n        _ = browserDetection\n    }\n\n    public func fetch(\n        cookieHeaderOverride _: String? = nil,\n        logger: ((String) -> Void)? = nil) async throws -> FactoryStatusSnapshot\n    {\n        _ = logger\n        throw FactoryStatusProbeError.notSupported\n    }\n}\n\n#endif\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Gemini/GeminiProviderDescriptor.swift",
    "content": "import CodexBarMacroSupport\nimport Foundation\n\n@ProviderDescriptorRegistration\n@ProviderDescriptorDefinition\npublic enum GeminiProviderDescriptor {\n    static func makeDescriptor() -> ProviderDescriptor {\n        ProviderDescriptor(\n            id: .gemini,\n            metadata: ProviderMetadata(\n                id: .gemini,\n                displayName: \"Gemini\",\n                sessionLabel: \"Pro\",\n                weeklyLabel: \"Flash\",\n                opusLabel: \"Flash Lite\",\n                supportsOpus: true,\n                supportsCredits: false,\n                creditsHint: \"\",\n                toggleTitle: \"Show Gemini usage\",\n                cliName: \"gemini\",\n                defaultEnabled: false,\n                isPrimaryProvider: false,\n                usesAccountFallback: false,\n                dashboardURL: \"https://gemini.google.com\",\n                statusPageURL: nil,\n                statusLinkURL: \"https://www.google.com/appsstatus/dashboard/products/npdyhgECDJ6tB66MxXyo/history\",\n                statusWorkspaceProductID: \"npdyhgECDJ6tB66MxXyo\"),\n            branding: ProviderBranding(\n                iconStyle: .gemini,\n                iconResourceName: \"ProviderIcon-gemini\",\n                color: ProviderColor(red: 171 / 255, green: 135 / 255, blue: 234 / 255)),\n            tokenCost: ProviderTokenCostConfig(\n                supportsTokenCost: false,\n                noDataMessage: { \"Gemini cost summary is not supported.\" }),\n            fetchPlan: ProviderFetchPlan(\n                sourceModes: [.auto, .api],\n                pipeline: ProviderFetchPipeline(resolveStrategies: { _ in [GeminiStatusFetchStrategy()] })),\n            cli: ProviderCLIConfig(\n                name: \"gemini\",\n                versionDetector: { _ in ProviderVersionDetector.geminiVersion() }))\n    }\n}\n\nstruct GeminiStatusFetchStrategy: ProviderFetchStrategy {\n    let id: String = \"gemini.api\"\n    let kind: ProviderFetchKind = .apiToken\n\n    func isAvailable(_: ProviderFetchContext) async -> Bool {\n        true\n    }\n\n    func fetch(_: ProviderFetchContext) async throws -> ProviderFetchResult {\n        let probe = GeminiStatusProbe()\n        let snap = try await probe.fetch()\n        return self.makeResult(\n            usage: snap.toUsageSnapshot(),\n            sourceLabel: \"api\")\n    }\n\n    func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool {\n        false\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Gemini/GeminiStatusProbe.swift",
    "content": "import Foundation\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n\npublic struct GeminiModelQuota: Sendable {\n    public let modelId: String\n    public let percentLeft: Double\n    public let resetTime: Date?\n    public let resetDescription: String?\n}\n\npublic struct GeminiStatusSnapshot: Sendable {\n    public let modelQuotas: [GeminiModelQuota]\n    public let rawText: String\n    public let accountEmail: String?\n    public let accountPlan: String?\n\n    // Convenience: lowest quota across all models (for icon display)\n    public var lowestPercentLeft: Double? {\n        self.modelQuotas.min(by: { $0.percentLeft < $1.percentLeft })?.percentLeft\n    }\n\n    /// Legacy compatibility\n    public var dailyPercentLeft: Double? {\n        self.lowestPercentLeft\n    }\n\n    public var resetDescription: String? {\n        self.modelQuotas.min(by: { $0.percentLeft < $1.percentLeft })?.resetDescription\n    }\n\n    /// Converts Gemini quotas to a unified UsageSnapshot.\n    /// Groups quotas by tier: Pro (24h window) as primary, Flash (24h window) as secondary,\n    /// Flash Lite (24h window) as tertiary.\n    public func toUsageSnapshot() -> UsageSnapshot {\n        let lower = self.modelQuotas.map { ($0.modelId.lowercased(), $0) }\n        let flashLiteQuotas = lower.filter { Self.isFlashLiteModel(id: $0.0) }.map(\\.1)\n        let flashQuotas = lower.filter { Self.isFlashModel(id: $0.0) }.map(\\.1)\n        let proQuotas = lower.filter { Self.isProModel(id: $0.0) }.map(\\.1)\n\n        let flashLiteMin = flashLiteQuotas.min(by: { $0.percentLeft < $1.percentLeft })\n        let flashMin = flashQuotas.min(by: { $0.percentLeft < $1.percentLeft })\n        let proMin = proQuotas.min(by: { $0.percentLeft < $1.percentLeft })\n\n        let primary = RateWindow(\n            usedPercent: proMin.map { 100 - $0.percentLeft } ?? 0,\n            windowMinutes: 1440,\n            resetsAt: proMin?.resetTime,\n            resetDescription: proMin?.resetDescription)\n\n        let secondary: RateWindow? = flashMin.map {\n            RateWindow(\n                usedPercent: 100 - $0.percentLeft,\n                windowMinutes: 1440,\n                resetsAt: $0.resetTime,\n                resetDescription: $0.resetDescription)\n        }\n        let tertiary: RateWindow? = flashLiteMin.map {\n            RateWindow(\n                usedPercent: 100 - $0.percentLeft,\n                windowMinutes: 1440,\n                resetsAt: $0.resetTime,\n                resetDescription: $0.resetDescription)\n        }\n\n        let identity = ProviderIdentitySnapshot(\n            providerID: .gemini,\n            accountEmail: self.accountEmail,\n            accountOrganization: nil,\n            loginMethod: self.accountPlan)\n        return UsageSnapshot(\n            primary: primary,\n            secondary: secondary,\n            tertiary: tertiary,\n            updatedAt: Date(),\n            identity: identity)\n    }\n\n    private static func isFlashLiteModel(id: String) -> Bool {\n        id.contains(\"flash-lite\")\n    }\n\n    private static func isFlashModel(id: String) -> Bool {\n        id.contains(\"flash\") && !self.isFlashLiteModel(id: id)\n    }\n\n    private static func isProModel(id: String) -> Bool {\n        id.contains(\"pro\")\n    }\n}\n\npublic enum GeminiStatusProbeError: LocalizedError, Sendable, Equatable {\n    case geminiNotInstalled\n    case notLoggedIn\n    case unsupportedAuthType(String)\n    case parseFailed(String)\n    case timedOut\n    case apiError(String)\n\n    public var errorDescription: String? {\n        switch self {\n        case .geminiNotInstalled:\n            \"Gemini CLI is not installed or not on PATH.\"\n        case .notLoggedIn:\n            \"Not logged in to Gemini. Run 'gemini' in Terminal to authenticate.\"\n        case let .unsupportedAuthType(authType):\n            \"Gemini \\(authType) auth not supported. Use Google account (OAuth) instead.\"\n        case let .parseFailed(msg):\n            \"Could not parse Gemini usage: \\(msg)\"\n        case .timedOut:\n            \"Gemini quota API request timed out.\"\n        case let .apiError(msg):\n            \"Gemini API error: \\(msg)\"\n        }\n    }\n}\n\npublic enum GeminiAuthType: String, Sendable {\n    case oauthPersonal = \"oauth-personal\"\n    case apiKey = \"api-key\"\n    case vertexAI = \"vertex-ai\"\n    case unknown\n}\n\n/// User tier IDs returned from the Cloud Code Private API (loadCodeAssist).\n/// Maps to: google3/cloud/developer_experience/cloudcode/pa/service/usertier.go\npublic enum GeminiUserTierId: String, Sendable {\n    case free = \"free-tier\"\n    case legacy = \"legacy-tier\"\n    case standard = \"standard-tier\"\n}\n\npublic struct GeminiStatusProbe: Sendable {\n    public var timeout: TimeInterval = 10.0\n    public var homeDirectory: String\n    public var dataLoader: @Sendable (URLRequest) async throws -> (Data, URLResponse)\n    private static let log = CodexBarLog.logger(LogCategories.geminiProbe)\n    private static let quotaEndpoint = \"https://cloudcode-pa.googleapis.com/v1internal:retrieveUserQuota\"\n    private static let loadCodeAssistEndpoint = \"https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist\"\n    private static let projectsEndpoint = \"https://cloudresourcemanager.googleapis.com/v1/projects\"\n    private static let credentialsPath = \"/.gemini/oauth_creds.json\"\n    private static let settingsPath = \"/.gemini/settings.json\"\n    private static let tokenRefreshEndpoint = \"https://oauth2.googleapis.com/token\"\n\n    public init(\n        timeout: TimeInterval = 10.0,\n        homeDirectory: String = NSHomeDirectory(),\n        dataLoader: @escaping @Sendable (URLRequest) async throws -> (Data, URLResponse) = { request in\n            try await URLSession.shared.data(for: request)\n        })\n    {\n        self.timeout = timeout\n        self.homeDirectory = homeDirectory\n        self.dataLoader = dataLoader\n    }\n\n    /// Reads the current Gemini auth type from settings.json\n    public static func currentAuthType(homeDirectory: String = NSHomeDirectory()) -> GeminiAuthType {\n        let settingsURL = URL(fileURLWithPath: homeDirectory + Self.settingsPath)\n\n        guard let data = try? Data(contentsOf: settingsURL),\n              let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],\n              let security = json[\"security\"] as? [String: Any],\n              let auth = security[\"auth\"] as? [String: Any],\n              let selectedType = auth[\"selectedType\"] as? String\n        else {\n            return .unknown\n        }\n\n        return GeminiAuthType(rawValue: selectedType) ?? .unknown\n    }\n\n    public func fetch() async throws -> GeminiStatusSnapshot {\n        // Block explicitly unsupported auth types; allow unknown to try OAuth creds\n        let authType = Self.currentAuthType(homeDirectory: self.homeDirectory)\n        switch authType {\n        case .apiKey:\n            throw GeminiStatusProbeError.unsupportedAuthType(\"API key\")\n        case .vertexAI:\n            throw GeminiStatusProbeError.unsupportedAuthType(\"Vertex AI\")\n        case .oauthPersonal, .unknown:\n            break\n        }\n\n        let snap = try await Self.fetchViaAPI(\n            timeout: self.timeout,\n            homeDirectory: self.homeDirectory,\n            dataLoader: self.dataLoader)\n\n        Self.log.info(\"Gemini API fetch ok\", metadata: [\n            \"dailyPercentLeft\": \"\\(snap.dailyPercentLeft ?? -1)\",\n        ])\n        return snap\n    }\n\n    // MARK: - Direct API approach\n\n    private static func fetchViaAPI(\n        timeout: TimeInterval,\n        homeDirectory: String,\n        dataLoader: @escaping @Sendable (URLRequest) async throws -> (Data, URLResponse)) async throws\n        -> GeminiStatusSnapshot\n    {\n        let creds = try Self.loadCredentials(homeDirectory: homeDirectory)\n\n        let expiryStr = creds.expiryDate.map { \"\\($0)\" } ?? \"nil\"\n        let hasRefresh = creds.refreshToken != nil\n        Self.log.debug(\"Token check\", metadata: [\n            \"expiry\": expiryStr,\n            \"hasRefresh\": hasRefresh ? \"1\" : \"0\",\n            \"now\": \"\\(Date())\",\n        ])\n\n        guard let storedAccessToken = creds.accessToken, !storedAccessToken.isEmpty else {\n            Self.log.error(\"No access token found\")\n            throw GeminiStatusProbeError.notLoggedIn\n        }\n\n        var accessToken = storedAccessToken\n        if let expiry = creds.expiryDate, expiry < Date() {\n            Self.log.info(\"Token expired; attempting refresh\", metadata: [\n                \"expiry\": \"\\(expiry)\",\n            ])\n\n            guard let refreshToken = creds.refreshToken else {\n                Self.log.error(\"No refresh token available\")\n                throw GeminiStatusProbeError.notLoggedIn\n            }\n\n            accessToken = try await Self.refreshAccessToken(\n                refreshToken: refreshToken,\n                timeout: timeout,\n                homeDirectory: homeDirectory,\n                dataLoader: dataLoader)\n        }\n\n        // Extract account info from JWT\n        let claims = Self.extractClaimsFromToken(creds.idToken)\n\n        // Load Code Assist status to get project ID and tier (aligned with CLI setupUser logic)\n        let caStatus = await Self.loadCodeAssistStatus(\n            accessToken: accessToken,\n            timeout: timeout,\n            dataLoader: dataLoader)\n\n        // Determine the project ID to use for quota fetching.\n        // Priority:\n        // 1. Project ID returned by loadCodeAssist (e.g. managed project for free tier)\n        // 2. Discovered project ID from cloud resource manager (e.g. user's own project)\n        var projectId = caStatus.projectId\n        if projectId == nil {\n            projectId = try? await Self.discoverGeminiProjectId(\n                accessToken: accessToken,\n                timeout: timeout,\n                dataLoader: dataLoader)\n        }\n\n        guard let url = URL(string: Self.quotaEndpoint) else {\n            throw GeminiStatusProbeError.apiError(\"Invalid endpoint URL\")\n        }\n\n        var request = URLRequest(url: url)\n        request.httpMethod = \"POST\"\n        request.setValue(\"Bearer \\(accessToken)\", forHTTPHeaderField: \"Authorization\")\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Content-Type\")\n\n        // Include project ID for accurate quota\n        if let projectId {\n            request.httpBody = Data(\"{\\\"project\\\": \\\"\\(projectId)\\\"}\".utf8)\n        } else {\n            request.httpBody = Data(\"{}\".utf8)\n        }\n        request.timeoutInterval = timeout\n\n        let (data, response) = try await dataLoader(request)\n\n        guard let httpResponse = response as? HTTPURLResponse else {\n            throw GeminiStatusProbeError.apiError(\"Invalid response\")\n        }\n\n        if httpResponse.statusCode == 401 {\n            throw GeminiStatusProbeError.notLoggedIn\n        }\n\n        guard httpResponse.statusCode == 200 else {\n            throw GeminiStatusProbeError.apiError(\"HTTP \\(httpResponse.statusCode)\")\n        }\n\n        let snapshot = try Self.parseAPIResponse(data, email: claims.email)\n\n        // Plan display strings with tier mapping:\n        // - standard-tier: Paid subscription (AI Pro, AI Ultra, Code Assist\n        //   Standard/Enterprise, Developer Program Premium)\n        // - free-tier + hd claim: Workspace account (Gemini included free since Jan 2025)\n        // - free-tier: Personal free account (1000 req/day limit)\n        // - legacy-tier: Unknown legacy/grandfathered tier\n        // - nil (API failed): Leave blank (no display)\n        let plan: String? = switch (caStatus.tier, claims.hostedDomain) {\n        case (.standard, _):\n            \"Paid\"\n        case let (.free, .some(domain)):\n            { Self.log.info(\"Workspace account detected\", metadata: [\"domain\": domain]); return \"Workspace\" }()\n        case (.free, .none):\n            { Self.log.info(\"Personal free account\"); return \"Free\" }()\n        case (.legacy, _):\n            \"Legacy\"\n        case (.none, _):\n            { Self.log.info(\"Tier detection failed, leaving plan blank\"); return nil }()\n        }\n\n        return GeminiStatusSnapshot(\n            modelQuotas: snapshot.modelQuotas,\n            rawText: snapshot.rawText,\n            accountEmail: snapshot.accountEmail,\n            accountPlan: plan)\n    }\n\n    private static func discoverGeminiProjectId(\n        accessToken: String,\n        timeout: TimeInterval,\n        dataLoader: @escaping @Sendable (URLRequest) async throws -> (Data, URLResponse)) async throws\n        -> String?\n    {\n        guard let url = URL(string: projectsEndpoint) else { return nil }\n\n        var request = URLRequest(url: url)\n        request.setValue(\"Bearer \\(accessToken)\", forHTTPHeaderField: \"Authorization\")\n        request.timeoutInterval = timeout\n\n        let (data, response) = try await dataLoader(request)\n\n        guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {\n            return nil\n        }\n\n        guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],\n              let projects = json[\"projects\"] as? [[String: Any]]\n        else {\n            return nil\n        }\n\n        // Look for Gemini API project (has \"generative-language\" label or \"gen-lang-client\" prefix)\n        for project in projects {\n            guard let projectId = project[\"projectId\"] as? String else { continue }\n\n            // Check for gen-lang-client prefix (Gemini CLI projects)\n            if projectId.hasPrefix(\"gen-lang-client\") {\n                return projectId\n            }\n\n            // Check for generative-language label\n            if let labels = project[\"labels\"] as? [String: String],\n               labels[\"generative-language\"] != nil\n            {\n                return projectId\n            }\n        }\n\n        return nil\n    }\n\n    private struct CodeAssistStatus {\n        let tier: GeminiUserTierId?\n        let projectId: String?\n\n        static let empty = CodeAssistStatus(tier: nil, projectId: nil)\n    }\n\n    private static func loadCodeAssistStatus(\n        accessToken: String,\n        timeout: TimeInterval,\n        dataLoader: @escaping @Sendable (URLRequest) async throws -> (Data, URLResponse)) async -> CodeAssistStatus\n    {\n        guard let url = URL(string: loadCodeAssistEndpoint) else {\n            self.log.warning(\"loadCodeAssist: invalid endpoint URL\")\n            return .empty\n        }\n\n        var request = URLRequest(url: url)\n        request.httpMethod = \"POST\"\n        request.setValue(\"Bearer \\(accessToken)\", forHTTPHeaderField: \"Authorization\")\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Content-Type\")\n        request.httpBody = Data(\"{\\\"metadata\\\":{\\\"ideType\\\":\\\"GEMINI_CLI\\\",\\\"pluginType\\\":\\\"GEMINI\\\"}}\".utf8)\n        request.timeoutInterval = timeout\n\n        let data: Data\n        let response: URLResponse\n        do {\n            (data, response) = try await dataLoader(request)\n        } catch {\n            Self.log.warning(\"loadCodeAssist: request failed\", metadata: [\"error\": \"\\(error)\"])\n            return .empty\n        }\n\n        guard let httpResponse = response as? HTTPURLResponse else {\n            Self.log.warning(\"loadCodeAssist: invalid response type\")\n            return .empty\n        }\n\n        guard httpResponse.statusCode == 200 else {\n            Self.log.warning(\"loadCodeAssist: HTTP error\", metadata: [\n                \"statusCode\": \"\\(httpResponse.statusCode)\",\n                \"body\": String(data: data, encoding: .utf8) ?? \"<binary>\",\n            ])\n            return .empty\n        }\n\n        guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {\n            Self.log.warning(\"loadCodeAssist: failed to parse JSON\", metadata: [\n                \"body\": String(data: data, encoding: .utf8) ?? \"<binary>\",\n            ])\n            return .empty\n        }\n\n        let rawProjectId: String? = {\n            if let project = json[\"cloudaicompanionProject\"] as? String {\n                return project\n            }\n            if let project = json[\"cloudaicompanionProject\"] as? [String: Any] {\n                if let projectId = project[\"id\"] as? String {\n                    return projectId\n                }\n                if let projectId = project[\"projectId\"] as? String {\n                    return projectId\n                }\n            }\n            return nil\n        }()\n        let trimmedProjectId = rawProjectId?.trimmingCharacters(in: .whitespacesAndNewlines)\n        let projectId = trimmedProjectId?.isEmpty == true ? nil : trimmedProjectId\n        if let projectId {\n            Self.log.info(\"loadCodeAssist: project detected\", metadata: [\"projectId\": projectId])\n        }\n\n        let tierId = (json[\"currentTier\"] as? [String: Any])?[\"id\"] as? String\n        guard let tierId else {\n            Self.log.warning(\"loadCodeAssist: no currentTier.id in response\", metadata: [\n                \"json\": \"\\(json)\",\n            ])\n            return CodeAssistStatus(tier: nil, projectId: projectId)\n        }\n\n        guard let tier = GeminiUserTierId(rawValue: tierId) else {\n            Self.log.warning(\"loadCodeAssist: unknown tier ID\", metadata: [\"tierId\": tierId])\n            return CodeAssistStatus(tier: nil, projectId: projectId)\n        }\n\n        Self.log.info(\"loadCodeAssist: success\", metadata: [\"tier\": tierId, \"projectId\": projectId ?? \"nil\"])\n        return CodeAssistStatus(tier: tier, projectId: projectId)\n    }\n\n    private struct OAuthCredentials {\n        let accessToken: String?\n        let idToken: String?\n        let refreshToken: String?\n        let expiryDate: Date?\n    }\n\n    private struct OAuthClientCredentials {\n        let clientId: String\n        let clientSecret: String\n    }\n\n    private static func extractOAuthCredentials() -> OAuthClientCredentials? {\n        let env = ProcessInfo.processInfo.environment\n\n        // Find the gemini binary\n        guard let geminiPath = BinaryLocator.resolveGeminiBinary(\n            env: env,\n            loginPATH: LoginShellPathCache.shared.current)\n            ?? TTYCommandRunner.which(\"gemini\")\n        else {\n            return nil\n        }\n\n        // Resolve symlinks to find the actual installation\n        let fm = FileManager.default\n        var realPath = geminiPath\n        if let resolved = try? fm.destinationOfSymbolicLink(atPath: geminiPath) {\n            if resolved.hasPrefix(\"/\") {\n                realPath = resolved\n            } else {\n                realPath = (geminiPath as NSString).deletingLastPathComponent + \"/\" + resolved\n            }\n        }\n\n        // Navigate from bin/gemini to the oauth2.js file\n        // Homebrew path: .../libexec/lib/node_modules/@google/gemini-cli/node_modules/@google/gemini-cli-core/dist/src/code_assist/oauth2.js\n        // Bun/npm path: .../node_modules/@google/gemini-cli-core/dist/src/code_assist/oauth2.js (sibling package)\n        let binDir = (realPath as NSString).deletingLastPathComponent\n        let baseDir = (binDir as NSString).deletingLastPathComponent\n\n        let oauthSubpath =\n            \"node_modules/@google/gemini-cli/node_modules/@google/gemini-cli-core/dist/src/code_assist/oauth2.js\"\n        let nixShareSubpath =\n            \"share/gemini-cli/node_modules/@google/gemini-cli-core/dist/src/code_assist/oauth2.js\"\n        let oauthFile = \"dist/src/code_assist/oauth2.js\"\n        let possiblePaths = [\n            // Homebrew nested structure\n            \"\\(baseDir)/libexec/lib/\\(oauthSubpath)\",\n            \"\\(baseDir)/lib/\\(oauthSubpath)\",\n            // Nix package layout\n            \"\\(baseDir)/\\(nixShareSubpath)\",\n            // Bun/npm sibling structure: gemini-cli-core is a sibling to gemini-cli\n            \"\\(baseDir)/../gemini-cli-core/\\(oauthFile)\",\n            // npm nested inside gemini-cli\n            \"\\(baseDir)/node_modules/@google/gemini-cli-core/\\(oauthFile)\",\n        ]\n\n        for path in possiblePaths {\n            if let content = try? String(contentsOfFile: path, encoding: .utf8) {\n                return self.parseOAuthCredentials(from: content)\n            }\n        }\n\n        return nil\n    }\n\n    private static func parseOAuthCredentials(from content: String) -> OAuthClientCredentials? {\n        // Match: const OAUTH_CLIENT_ID = '...';\n        let clientIdPattern = #\"OAUTH_CLIENT_ID\\s*=\\s*['\"]([\\w\\-\\.]+)['\"]\\s*;\"#\n        let secretPattern = #\"OAUTH_CLIENT_SECRET\\s*=\\s*['\"]([\\w\\-]+)['\"]\\s*;\"#\n\n        guard let clientIdRegex = try? NSRegularExpression(pattern: clientIdPattern),\n              let secretRegex = try? NSRegularExpression(pattern: secretPattern)\n        else {\n            return nil\n        }\n\n        let range = NSRange(content.startIndex..., in: content)\n\n        guard let clientIdMatch = clientIdRegex.firstMatch(in: content, range: range),\n              let clientIdRange = Range(clientIdMatch.range(at: 1), in: content),\n              let secretMatch = secretRegex.firstMatch(in: content, range: range),\n              let secretRange = Range(secretMatch.range(at: 1), in: content)\n        else {\n            return nil\n        }\n\n        let clientId = String(content[clientIdRange])\n        let clientSecret = String(content[secretRange])\n\n        return OAuthClientCredentials(clientId: clientId, clientSecret: clientSecret)\n    }\n\n    private static func refreshAccessToken(\n        refreshToken: String,\n        timeout: TimeInterval,\n        homeDirectory: String,\n        dataLoader: @escaping @Sendable (URLRequest) async throws -> (Data, URLResponse)) async throws\n        -> String\n    {\n        guard let url = URL(string: tokenRefreshEndpoint) else {\n            throw GeminiStatusProbeError.apiError(\"Invalid token refresh URL\")\n        }\n\n        var request = URLRequest(url: url)\n        request.httpMethod = \"POST\"\n        request.setValue(\"application/x-www-form-urlencoded\", forHTTPHeaderField: \"Content-Type\")\n        request.timeoutInterval = timeout\n\n        guard let oauthCreds = Self.extractOAuthCredentials() else {\n            Self.log.error(\"Could not extract OAuth credentials from Gemini CLI\")\n            throw GeminiStatusProbeError.apiError(\"Could not find Gemini CLI OAuth configuration\")\n        }\n\n        let body = [\n            \"client_id=\\(oauthCreds.clientId)\",\n            \"client_secret=\\(oauthCreds.clientSecret)\",\n            \"refresh_token=\\(refreshToken)\",\n            \"grant_type=refresh_token\",\n        ].joined(separator: \"&\")\n        request.httpBody = body.data(using: .utf8)\n\n        let (data, response) = try await dataLoader(request)\n\n        guard let httpResponse = response as? HTTPURLResponse else {\n            throw GeminiStatusProbeError.apiError(\"Invalid refresh response\")\n        }\n\n        guard httpResponse.statusCode == 200 else {\n            Self.log.error(\"Token refresh failed\", metadata: [\n                \"statusCode\": \"\\(httpResponse.statusCode)\",\n            ])\n            throw GeminiStatusProbeError.notLoggedIn\n        }\n\n        guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],\n              let newAccessToken = json[\"access_token\"] as? String\n        else {\n            throw GeminiStatusProbeError.parseFailed(\"Could not parse refresh response\")\n        }\n\n        // Update stored credentials with new token\n        try Self.updateStoredCredentials(json, homeDirectory: homeDirectory)\n\n        Self.log.info(\"Token refreshed successfully\")\n        return newAccessToken\n    }\n\n    private static func updateStoredCredentials(_ refreshResponse: [String: Any], homeDirectory: String) throws {\n        let credsURL = URL(fileURLWithPath: homeDirectory + Self.credentialsPath)\n\n        guard let existingCreds = try? Data(contentsOf: credsURL),\n              var json = try? JSONSerialization.jsonObject(with: existingCreds) as? [String: Any]\n        else {\n            return\n        }\n\n        // Update with new values from refresh response\n        if let accessToken = refreshResponse[\"access_token\"] {\n            json[\"access_token\"] = accessToken\n        }\n        if let expiresIn = refreshResponse[\"expires_in\"] as? Double {\n            json[\"expiry_date\"] = (Date().timeIntervalSince1970 + expiresIn) * 1000\n        }\n        if let idToken = refreshResponse[\"id_token\"] {\n            json[\"id_token\"] = idToken\n        }\n\n        let updatedData = try JSONSerialization.data(withJSONObject: json, options: [.prettyPrinted])\n        try updatedData.write(to: credsURL, options: .atomic)\n    }\n\n    private static func loadCredentials(homeDirectory: String) throws -> OAuthCredentials {\n        let credsURL = URL(fileURLWithPath: homeDirectory + Self.credentialsPath)\n\n        guard FileManager.default.fileExists(atPath: credsURL.path) else {\n            throw GeminiStatusProbeError.notLoggedIn\n        }\n\n        let data = try Data(contentsOf: credsURL)\n        guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {\n            throw GeminiStatusProbeError.parseFailed(\"Invalid credentials file\")\n        }\n\n        let accessToken = json[\"access_token\"] as? String\n        let idToken = json[\"id_token\"] as? String\n        let refreshToken = json[\"refresh_token\"] as? String\n\n        var expiryDate: Date?\n        if let expiryMs = json[\"expiry_date\"] as? Double {\n            expiryDate = Date(timeIntervalSince1970: expiryMs / 1000)\n        }\n\n        return OAuthCredentials(\n            accessToken: accessToken,\n            idToken: idToken,\n            refreshToken: refreshToken,\n            expiryDate: expiryDate)\n    }\n\n    private struct TokenClaims {\n        let email: String?\n        let hostedDomain: String?\n    }\n\n    private static func extractClaimsFromToken(_ idToken: String?) -> TokenClaims {\n        guard let token = idToken else { return TokenClaims(email: nil, hostedDomain: nil) }\n\n        let parts = token.components(separatedBy: \".\")\n        guard parts.count >= 2 else { return TokenClaims(email: nil, hostedDomain: nil) }\n\n        var payload = parts[1]\n            .replacingOccurrences(of: \"-\", with: \"+\")\n            .replacingOccurrences(of: \"_\", with: \"/\")\n\n        let remainder = payload.count % 4\n        if remainder > 0 {\n            payload += String(repeating: \"=\", count: 4 - remainder)\n        }\n\n        guard let data = Data(base64Encoded: payload, options: .ignoreUnknownCharacters),\n              let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any]\n        else {\n            return TokenClaims(email: nil, hostedDomain: nil)\n        }\n\n        return TokenClaims(\n            email: json[\"email\"] as? String,\n            hostedDomain: json[\"hd\"] as? String)\n    }\n\n    private static func extractEmailFromToken(_ idToken: String?) -> String? {\n        self.extractClaimsFromToken(idToken).email\n    }\n\n    private struct QuotaBucket: Decodable {\n        let remainingFraction: Double?\n        let resetTime: String?\n        let modelId: String?\n        let tokenType: String?\n    }\n\n    private struct QuotaResponse: Decodable {\n        let buckets: [QuotaBucket]?\n    }\n\n    private static func parseAPIResponse(_ data: Data, email: String?) throws -> GeminiStatusSnapshot {\n        let decoder = JSONDecoder()\n        let response = try decoder.decode(QuotaResponse.self, from: data)\n\n        guard let buckets = response.buckets, !buckets.isEmpty else {\n            throw GeminiStatusProbeError.parseFailed(\"No quota buckets in response\")\n        }\n\n        // Group quotas by model, keeping lowest per model (input tokens usually)\n        var modelQuotaMap: [String: (fraction: Double, resetString: String?)] = [:]\n\n        for bucket in buckets {\n            guard let modelId = bucket.modelId, let fraction = bucket.remainingFraction else { continue }\n\n            if let existing = modelQuotaMap[modelId] {\n                if fraction < existing.fraction {\n                    modelQuotaMap[modelId] = (fraction, bucket.resetTime)\n                }\n            } else {\n                modelQuotaMap[modelId] = (fraction, bucket.resetTime)\n            }\n        }\n\n        // Convert to sorted array (by model name for consistent ordering)\n        let quotas = modelQuotaMap\n            .sorted { $0.key < $1.key }\n            .map { modelId, info in\n                let resetDate = info.resetString.flatMap { Self.parseResetTime($0) }\n                return GeminiModelQuota(\n                    modelId: modelId,\n                    percentLeft: info.fraction * 100,\n                    resetTime: resetDate,\n                    resetDescription: info.resetString.flatMap { Self.formatResetTime($0) })\n            }\n\n        let rawText = String(data: data, encoding: .utf8) ?? \"\"\n\n        return GeminiStatusSnapshot(\n            modelQuotas: quotas,\n            rawText: rawText,\n            accountEmail: email,\n            accountPlan: nil)\n    }\n\n    private static func parseResetTime(_ isoString: String) -> Date? {\n        let formatter = ISO8601DateFormatter()\n        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]\n\n        if let date = formatter.date(from: isoString) {\n            return date\n        }\n        formatter.formatOptions = [.withInternetDateTime]\n        return formatter.date(from: isoString)\n    }\n\n    private static func formatResetTime(_ isoString: String) -> String {\n        guard let resetDate = parseResetTime(isoString) else {\n            return \"Resets soon\"\n        }\n\n        let now = Date()\n        let interval = resetDate.timeIntervalSince(now)\n\n        if interval <= 0 {\n            return \"Resets soon\"\n        }\n\n        let hours = Int(interval / 3600)\n        let minutes = Int((interval.truncatingRemainder(dividingBy: 3600)) / 60)\n\n        if hours > 0 {\n            return \"Resets in \\(hours)h \\(minutes)m\"\n        } else {\n            return \"Resets in \\(minutes)m\"\n        }\n    }\n\n    // MARK: - Legacy CLI parsing (kept for fallback)\n\n    public static func parse(text: String) throws -> GeminiStatusSnapshot {\n        let clean = TextParsing.stripANSICodes(text)\n        guard !clean.isEmpty else { throw GeminiStatusProbeError.timedOut }\n\n        let quotas = Self.parseModelUsageTable(clean)\n\n        if quotas.isEmpty {\n            if clean.contains(\"Login with Google\") || clean.contains(\"Use Gemini API key\") {\n                throw GeminiStatusProbeError.notLoggedIn\n            }\n            if clean.contains(\"Waiting for auth\"), !clean.contains(\"Usage\") {\n                throw GeminiStatusProbeError.notLoggedIn\n            }\n            throw GeminiStatusProbeError.parseFailed(\"No usage data found in /stats output\")\n        }\n\n        return GeminiStatusSnapshot(\n            modelQuotas: quotas,\n            rawText: text,\n            accountEmail: nil,\n            accountPlan: nil)\n    }\n\n    private static func parseModelUsageTable(_ text: String) -> [GeminiModelQuota] {\n        let lines = text.components(separatedBy: .newlines)\n        var quotas: [GeminiModelQuota] = []\n\n        let pattern = #\"(gemini[-\\w.]+)\\s+[\\d-]+\\s+([0-9]+(?:\\.[0-9]+)?)\\s*%\\s*\\(([^)]+)\\)\"#\n        guard let regex = try? NSRegularExpression(pattern: pattern, options: [.caseInsensitive]) else {\n            return []\n        }\n\n        for line in lines {\n            let cleanLine = line.replacingOccurrences(of: \"│\", with: \" \")\n            let range = NSRange(cleanLine.startIndex..<cleanLine.endIndex, in: cleanLine)\n            guard let match = regex.firstMatch(in: cleanLine, options: [], range: range),\n                  match.numberOfRanges >= 4 else { continue }\n\n            guard let modelRange = Range(match.range(at: 1), in: cleanLine),\n                  let pctRange = Range(match.range(at: 2), in: cleanLine),\n                  let pct = Double(cleanLine[pctRange])\n            else { continue }\n\n            let modelId = String(cleanLine[modelRange])\n            var resetDesc: String?\n            if let resetRange = Range(match.range(at: 3), in: cleanLine) {\n                resetDesc = String(cleanLine[resetRange]).trimmingCharacters(in: .whitespaces)\n            }\n\n            quotas.append(GeminiModelQuota(\n                modelId: modelId,\n                percentLeft: pct,\n                resetTime: nil,\n                resetDescription: resetDesc))\n        }\n\n        return quotas\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/JetBrains/JetBrainsIDEDetector.swift",
    "content": "import Foundation\n\npublic struct JetBrainsIDEInfo: Sendable, Equatable, Hashable {\n    public let name: String\n    public let version: String\n    public let basePath: String\n    public let quotaFilePath: String\n\n    public init(name: String, version: String, basePath: String, quotaFilePath: String) {\n        self.name = name\n        self.version = version\n        self.basePath = basePath\n        self.quotaFilePath = quotaFilePath\n    }\n\n    public var displayName: String {\n        \"\\(self.name) \\(self.version)\"\n    }\n}\n\npublic enum JetBrainsIDEDetector {\n    private static let idePatterns: [(prefix: String, displayName: String)] = [\n        (\"IntelliJIdea\", \"IntelliJ IDEA\"),\n        (\"PyCharm\", \"PyCharm\"),\n        (\"WebStorm\", \"WebStorm\"),\n        (\"GoLand\", \"GoLand\"),\n        (\"CLion\", \"CLion\"),\n        (\"DataGrip\", \"DataGrip\"),\n        (\"RubyMine\", \"RubyMine\"),\n        (\"Rider\", \"Rider\"),\n        (\"PhpStorm\", \"PhpStorm\"),\n        (\"AppCode\", \"AppCode\"),\n        (\"Fleet\", \"Fleet\"),\n        (\"AndroidStudio\", \"Android Studio\"),\n        (\"RustRover\", \"RustRover\"),\n        (\"Aqua\", \"Aqua\"),\n        (\"DataSpell\", \"DataSpell\"),\n    ]\n\n    private static let quotaFileName = \"AIAssistantQuotaManager2.xml\"\n\n    public static func detectInstalledIDEs(includeMissingQuota: Bool = false) -> [JetBrainsIDEInfo] {\n        let basePaths = self.jetBrainsConfigBasePaths()\n        var detectedIDEs: [JetBrainsIDEInfo] = []\n\n        let fileManager = FileManager.default\n        for basePath in basePaths {\n            guard fileManager.fileExists(atPath: basePath) else { continue }\n            guard let contents = try? fileManager.contentsOfDirectory(atPath: basePath) else { continue }\n\n            for dirname in contents {\n                guard let ideInfo = parseIDEDirectory(dirname: dirname, basePath: basePath) else { continue }\n                if includeMissingQuota || fileManager.fileExists(atPath: ideInfo.quotaFilePath) {\n                    detectedIDEs.append(ideInfo)\n                }\n            }\n        }\n\n        return detectedIDEs.sorted { lhs, rhs in\n            if lhs.name == rhs.name {\n                return self.compareVersions(lhs.version, rhs.version) > 0\n            }\n            return lhs.name < rhs.name\n        }\n    }\n\n    public static func detectLatestIDE() -> JetBrainsIDEInfo? {\n        let ides = self.detectInstalledIDEs()\n        guard !ides.isEmpty else { return nil }\n\n        let fileManager = FileManager.default\n        var latestIDE: JetBrainsIDEInfo?\n        var latestModificationDate: Date?\n\n        for ide in ides {\n            guard let attrs = try? fileManager.attributesOfItem(atPath: ide.quotaFilePath),\n                  let modDate = attrs[.modificationDate] as? Date\n            else { continue }\n\n            if latestModificationDate == nil || modDate > latestModificationDate! {\n                latestModificationDate = modDate\n                latestIDE = ide\n            }\n        }\n\n        return latestIDE ?? ides.first\n    }\n\n    private static func jetBrainsConfigBasePaths() -> [String] {\n        let homeDir = FileManager.default.homeDirectoryForCurrentUser.path\n        #if os(macOS)\n        return [\n            \"\\(homeDir)/Library/Application Support/JetBrains\",\n            \"\\(homeDir)/Library/Application Support/Google\",\n        ]\n        #else\n        return [\n            \"\\(homeDir)/.config/JetBrains\",\n            \"\\(homeDir)/.local/share/JetBrains\",\n            \"\\(homeDir)/.config/Google\",\n        ]\n        #endif\n    }\n\n    private static func parseIDEDirectory(dirname: String, basePath: String) -> JetBrainsIDEInfo? {\n        let dirnameLower = dirname.lowercased()\n        for (prefix, displayName) in self.idePatterns {\n            let prefixLower = prefix.lowercased()\n            guard dirnameLower.hasPrefix(prefixLower) else { continue }\n            let versionPart = String(dirname.dropFirst(prefix.count))\n            let version = versionPart.isEmpty ? \"Unknown\" : versionPart\n            let idePath = \"\\(basePath)/\\(dirname)\"\n            let quotaFilePath = quotaFilePath(for: idePath)\n            return JetBrainsIDEInfo(\n                name: displayName,\n                version: version,\n                basePath: idePath,\n                quotaFilePath: quotaFilePath)\n        }\n        return nil\n    }\n\n    #if DEBUG\n\n    // MARK: - Test hooks (DEBUG-only)\n\n    public static func _parseIDEDirectoryForTesting(dirname: String, basePath: String) -> JetBrainsIDEInfo? {\n        self.parseIDEDirectory(dirname: dirname, basePath: basePath)\n    }\n    #endif\n\n    public static func quotaFilePath(for ideBasePath: String) -> String {\n        \"\\(ideBasePath)/options/\\(self.quotaFileName)\"\n    }\n\n    private static func compareVersions(_ v1: String, _ v2: String) -> Int {\n        let parts1 = v1.split(separator: \".\").compactMap { Int($0) }\n        let parts2 = v2.split(separator: \".\").compactMap { Int($0) }\n\n        let maxLen = max(parts1.count, parts2.count)\n        for i in 0..<maxLen {\n            let p1 = i < parts1.count ? parts1[i] : 0\n            let p2 = i < parts2.count ? parts2[i] : 0\n            if p1 != p2 {\n                return p1 - p2\n            }\n        }\n        return 0\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/JetBrains/JetBrainsProviderDescriptor.swift",
    "content": "import CodexBarMacroSupport\nimport Foundation\n\n@ProviderDescriptorRegistration\n@ProviderDescriptorDefinition\npublic enum JetBrainsProviderDescriptor {\n    static func makeDescriptor() -> ProviderDescriptor {\n        ProviderDescriptor(\n            id: .jetbrains,\n            metadata: ProviderMetadata(\n                id: .jetbrains,\n                displayName: \"JetBrains AI\",\n                sessionLabel: \"Current\",\n                weeklyLabel: \"Refill\",\n                opusLabel: nil,\n                supportsOpus: false,\n                supportsCredits: false,\n                creditsHint: \"\",\n                toggleTitle: \"Show JetBrains AI usage\",\n                cliName: \"jetbrains\",\n                defaultEnabled: false,\n                isPrimaryProvider: false,\n                usesAccountFallback: false,\n                dashboardURL: nil,\n                statusPageURL: nil),\n            branding: ProviderBranding(\n                iconStyle: .jetbrains,\n                iconResourceName: \"ProviderIcon-jetbrains\",\n                color: ProviderColor(red: 255 / 255, green: 51 / 255, blue: 153 / 255)),\n            tokenCost: ProviderTokenCostConfig(\n                supportsTokenCost: false,\n                noDataMessage: { \"JetBrains AI cost summary is not supported.\" }),\n            fetchPlan: ProviderFetchPlan(\n                sourceModes: [.auto, .cli],\n                pipeline: ProviderFetchPipeline(resolveStrategies: { _ in [JetBrainsStatusFetchStrategy()] })),\n            cli: ProviderCLIConfig(\n                name: \"jetbrains\",\n                versionDetector: nil))\n    }\n}\n\nstruct JetBrainsStatusFetchStrategy: ProviderFetchStrategy {\n    let id: String = \"jetbrains.local\"\n    let kind: ProviderFetchKind = .localProbe\n\n    func isAvailable(_: ProviderFetchContext) async -> Bool {\n        true\n    }\n\n    func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {\n        let probe = JetBrainsStatusProbe(settings: context.settings)\n        let snap = try await probe.fetch()\n        let usage = try snap.toUsageSnapshot()\n        return self.makeResult(\n            usage: usage,\n            sourceLabel: \"local\")\n    }\n\n    func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool {\n        false\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/JetBrains/JetBrainsStatusProbe.swift",
    "content": "import Foundation\n#if canImport(FoundationXML)\nimport FoundationXML\n#endif\n\npublic struct JetBrainsQuotaInfo: Sendable, Equatable {\n    public let type: String?\n    public let used: Double\n    public let maximum: Double\n    public let available: Double\n    public let until: Date?\n\n    public init(type: String?, used: Double, maximum: Double, available: Double?, until: Date?) {\n        self.type = type\n        self.used = used\n        self.maximum = maximum\n        // Use available if provided, otherwise calculate from maximum - used\n        self.available = available ?? max(0, maximum - used)\n        self.until = until\n    }\n\n    /// Percentage of quota that has been used (0-100)\n    public var usedPercent: Double {\n        guard self.maximum > 0 else { return 0 }\n        return min(100, max(0, (self.used / self.maximum) * 100))\n    }\n\n    /// Percentage of quota remaining (0-100), based on available value\n    public var remainingPercent: Double {\n        guard self.maximum > 0 else { return 100 }\n        return min(100, max(0, (self.available / self.maximum) * 100))\n    }\n}\n\npublic struct JetBrainsRefillInfo: Sendable, Equatable {\n    public let type: String?\n    public let next: Date?\n    public let amount: Double?\n    public let duration: String?\n\n    public init(type: String?, next: Date?, amount: Double?, duration: String?) {\n        self.type = type\n        self.next = next\n        self.amount = amount\n        self.duration = duration\n    }\n}\n\npublic struct JetBrainsStatusSnapshot: Sendable {\n    public let quotaInfo: JetBrainsQuotaInfo\n    public let refillInfo: JetBrainsRefillInfo?\n    public let detectedIDE: JetBrainsIDEInfo?\n\n    public init(quotaInfo: JetBrainsQuotaInfo, refillInfo: JetBrainsRefillInfo?, detectedIDE: JetBrainsIDEInfo?) {\n        self.quotaInfo = quotaInfo\n        self.refillInfo = refillInfo\n        self.detectedIDE = detectedIDE\n    }\n\n    public func toUsageSnapshot() throws -> UsageSnapshot {\n        // Primary shows monthly credits usage with next refill date\n        // IDE displays: \"今月のクレジット残り X / Y\" with \"Z月D日に更新されます\"\n        let refillDate = self.refillInfo?.next\n        let primary = RateWindow(\n            usedPercent: self.quotaInfo.usedPercent,\n            windowMinutes: nil,\n            resetsAt: refillDate,\n            resetDescription: Self.formatResetDescription(refillDate))\n\n        let identity = ProviderIdentitySnapshot(\n            providerID: .jetbrains,\n            accountEmail: nil,\n            accountOrganization: self.detectedIDE?.displayName,\n            loginMethod: self.quotaInfo.type)\n\n        return UsageSnapshot(\n            primary: primary,\n            secondary: nil,\n            tertiary: nil,\n            updatedAt: Date(),\n            identity: identity)\n    }\n\n    private static func formatResetDescription(_ date: Date?) -> String? {\n        guard let date else { return nil }\n        let now = Date()\n        let interval = date.timeIntervalSince(now)\n        guard interval > 0 else { return \"Expired\" }\n\n        let hours = Int(interval / 3600)\n        let minutes = Int((interval.truncatingRemainder(dividingBy: 3600)) / 60)\n\n        if hours > 24 {\n            let days = hours / 24\n            let remainingHours = hours % 24\n            return \"Resets in \\(days)d \\(remainingHours)h\"\n        } else if hours > 0 {\n            return \"Resets in \\(hours)h \\(minutes)m\"\n        } else {\n            return \"Resets in \\(minutes)m\"\n        }\n    }\n}\n\npublic enum JetBrainsStatusProbeError: LocalizedError, Sendable, Equatable {\n    case noIDEDetected\n    case quotaFileNotFound(String)\n    case parseError(String)\n    case noQuotaInfo\n\n    public var errorDescription: String? {\n        switch self {\n        case .noIDEDetected:\n            \"No JetBrains IDE with AI Assistant detected. Install a JetBrains IDE and enable AI Assistant.\"\n        case let .quotaFileNotFound(path):\n            \"JetBrains AI quota file not found at \\(path). Enable AI Assistant in your IDE.\"\n        case let .parseError(message):\n            \"Could not parse JetBrains AI quota: \\(message)\"\n        case .noQuotaInfo:\n            \"No quota information found in the JetBrains AI configuration.\"\n        }\n    }\n}\n\npublic struct JetBrainsStatusProbe: Sendable {\n    private let settings: ProviderSettingsSnapshot?\n\n    public init(settings: ProviderSettingsSnapshot? = nil) {\n        self.settings = settings\n    }\n\n    public func fetch() async throws -> JetBrainsStatusSnapshot {\n        let (quotaFilePath, detectedIDE) = try self.resolveQuotaFilePath()\n        return try Self.parseQuotaFile(at: quotaFilePath, detectedIDE: detectedIDE)\n    }\n\n    private func resolveQuotaFilePath() throws -> (String, JetBrainsIDEInfo?) {\n        if let customPath = self.settings?.jetbrainsIDEBasePath?.trimmingCharacters(in: .whitespacesAndNewlines),\n           !customPath.isEmpty\n        {\n            let expandedBasePath = (customPath as NSString).expandingTildeInPath\n            let quotaPath = JetBrainsIDEDetector.quotaFilePath(for: expandedBasePath)\n            return (quotaPath, nil)\n        }\n\n        guard let detectedIDE = JetBrainsIDEDetector.detectLatestIDE() else {\n            throw JetBrainsStatusProbeError.noIDEDetected\n        }\n        return (detectedIDE.quotaFilePath, detectedIDE)\n    }\n\n    public static func parseQuotaFile(\n        at path: String,\n        detectedIDE: JetBrainsIDEInfo?) throws -> JetBrainsStatusSnapshot\n    {\n        guard FileManager.default.fileExists(atPath: path) else {\n            throw JetBrainsStatusProbeError.quotaFileNotFound(path)\n        }\n\n        let xmlData: Data\n        do {\n            xmlData = try Data(contentsOf: URL(fileURLWithPath: path))\n        } catch {\n            throw JetBrainsStatusProbeError.parseError(\"Failed to read file: \\(error.localizedDescription)\")\n        }\n\n        return try Self.parseXMLData(xmlData, detectedIDE: detectedIDE)\n    }\n\n    public static func parseXMLData(_ data: Data, detectedIDE: JetBrainsIDEInfo?) throws -> JetBrainsStatusSnapshot {\n        #if os(macOS)\n        let document: XMLDocument\n        do {\n            document = try XMLDocument(data: data)\n        } catch {\n            throw JetBrainsStatusProbeError.parseError(\"Invalid XML: \\(error.localizedDescription)\")\n        }\n\n        let quotaInfoRaw = try? document\n            .nodes(forXPath: \"//component[@name='AIAssistantQuotaManager2']/option[@name='quotaInfo']/@value\")\n            .first?\n            .stringValue\n        let nextRefillRaw = try? document\n            .nodes(forXPath: \"//component[@name='AIAssistantQuotaManager2']/option[@name='nextRefill']/@value\")\n            .first?\n            .stringValue\n        #else\n        let parseResult = JetBrainsXMLParser.parse(data: data)\n        let quotaInfoRaw = parseResult.quotaInfo\n        let nextRefillRaw = parseResult.nextRefill\n        #endif\n\n        guard let quotaInfoRaw, !quotaInfoRaw.isEmpty else {\n            throw JetBrainsStatusProbeError.noQuotaInfo\n        }\n\n        let quotaInfoDecoded = Self.decodeHTMLEntities(quotaInfoRaw)\n        let quotaInfo = try Self.parseQuotaInfoJSON(quotaInfoDecoded)\n\n        var refillInfo: JetBrainsRefillInfo?\n        if let nextRefillRaw, !nextRefillRaw.isEmpty {\n            let nextRefillDecoded = Self.decodeHTMLEntities(nextRefillRaw)\n            refillInfo = try? Self.parseRefillInfoJSON(nextRefillDecoded)\n        }\n\n        return JetBrainsStatusSnapshot(\n            quotaInfo: quotaInfo,\n            refillInfo: refillInfo,\n            detectedIDE: detectedIDE)\n    }\n\n    private static func decodeHTMLEntities(_ string: String) -> String {\n        string\n            .replacingOccurrences(of: \"&#10;\", with: \"\\n\")\n            .replacingOccurrences(of: \"&quot;\", with: \"\\\"\")\n            .replacingOccurrences(of: \"&amp;\", with: \"&\")\n            .replacingOccurrences(of: \"&lt;\", with: \"<\")\n            .replacingOccurrences(of: \"&gt;\", with: \">\")\n            .replacingOccurrences(of: \"&apos;\", with: \"'\")\n    }\n\n    private static func parseQuotaInfoJSON(_ jsonString: String) throws -> JetBrainsQuotaInfo {\n        guard let data = jsonString.data(using: .utf8) else {\n            throw JetBrainsStatusProbeError.parseError(\"Invalid JSON encoding\")\n        }\n\n        guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {\n            throw JetBrainsStatusProbeError.parseError(\"Invalid JSON format\")\n        }\n\n        let type = json[\"type\"] as? String\n        let currentStr = json[\"current\"] as? String\n        let maximumStr = json[\"maximum\"] as? String\n        let untilStr = json[\"until\"] as? String\n\n        // tariffQuota contains the actual available credits\n        let tariffQuota = json[\"tariffQuota\"] as? [String: Any]\n        let availableStr = tariffQuota?[\"available\"] as? String\n\n        let used = currentStr.flatMap { Double($0) } ?? 0\n        let maximum = maximumStr.flatMap { Double($0) } ?? 0\n        let available = availableStr.flatMap { Double($0) }\n        let until = untilStr.flatMap { Self.parseDate($0) }\n\n        return JetBrainsQuotaInfo(type: type, used: used, maximum: maximum, available: available, until: until)\n    }\n\n    private static func parseRefillInfoJSON(_ jsonString: String) throws -> JetBrainsRefillInfo {\n        guard let data = jsonString.data(using: .utf8) else {\n            throw JetBrainsStatusProbeError.parseError(\"Invalid JSON encoding\")\n        }\n\n        guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {\n            throw JetBrainsStatusProbeError.parseError(\"Invalid JSON format\")\n        }\n\n        let type = json[\"type\"] as? String\n        let nextStr = json[\"next\"] as? String\n        let amountStr = json[\"amount\"] as? String\n        let duration = json[\"duration\"] as? String\n\n        let next = nextStr.flatMap { Self.parseDate($0) }\n        let amount = amountStr.flatMap { Double($0) }\n\n        let tariff = json[\"tariff\"] as? [String: Any]\n        let tariffAmountStr = tariff?[\"amount\"] as? String\n        let tariffDuration = tariff?[\"duration\"] as? String\n        let finalAmount = amount ?? tariffAmountStr.flatMap { Double($0) }\n        let finalDuration = duration ?? tariffDuration\n\n        return JetBrainsRefillInfo(type: type, next: next, amount: finalAmount, duration: finalDuration)\n    }\n\n    private static func parseDate(_ string: String) -> Date? {\n        let formatter = ISO8601DateFormatter()\n        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]\n        if let date = formatter.date(from: string) {\n            return date\n        }\n\n        formatter.formatOptions = [.withInternetDateTime]\n        return formatter.date(from: string)\n    }\n}\n\n#if !os(macOS)\n/// Simple regex-based XML parser to avoid libxml2 dependency on Linux.\n/// Only extracts quotaInfo and nextRefill values from AIAssistantQuotaManager2 component.\nprivate enum JetBrainsXMLParser {\n    struct ParseResult {\n        let quotaInfo: String?\n        let nextRefill: String?\n    }\n\n    static func parse(data: Data) -> ParseResult {\n        guard let content = String(data: data, encoding: .utf8) else {\n            return ParseResult(quotaInfo: nil, nextRefill: nil)\n        }\n\n        // Find the AIAssistantQuotaManager2 component block\n        guard let componentRange = self.findComponentRange(in: content) else {\n            return ParseResult(quotaInfo: nil, nextRefill: nil)\n        }\n\n        let componentContent = String(content[componentRange])\n\n        let quotaInfo = self.extractOptionValue(named: \"quotaInfo\", from: componentContent)\n        let nextRefill = self.extractOptionValue(named: \"nextRefill\", from: componentContent)\n\n        return ParseResult(quotaInfo: quotaInfo, nextRefill: nextRefill)\n    }\n\n    private static func findComponentRange(in content: String) -> Range<String.Index>? {\n        // Match <component name=\"AIAssistantQuotaManager2\"> ... </component>\n        let pattern = #\"<component[^>]*name\\s*=\\s*[\"']AIAssistantQuotaManager2[\"'][^>]*>[\\s\\S]*?</component>\"#\n        guard let regex = try? NSRegularExpression(pattern: pattern, options: []),\n              let match = regex.firstMatch(\n                  in: content,\n                  options: [],\n                  range: NSRange(content.startIndex..., in: content)),\n              let range = Range(match.range, in: content)\n        else {\n            return nil\n        }\n        return range\n    }\n\n    private static func extractOptionValue(named name: String, from content: String) -> String? {\n        // Match <option name=\"NAME\" value=\"VALUE\"/> or <option value=\"VALUE\" name=\"NAME\"/>\n        let patterns = [\n            #\"<option[^>]*name\\s*=\\s*[\"']\\#(name)[\"'][^>]*value\\s*=\\s*[\"']([^\"']*)[\"']\"#,\n            #\"<option[^>]*value\\s*=\\s*[\"']([^\"']*)[\"'][^>]*name\\s*=\\s*[\"']\\#(name)[\"']\"#,\n        ]\n\n        for pattern in patterns {\n            guard let regex = try? NSRegularExpression(pattern: pattern, options: []),\n                  let match = regex.firstMatch(\n                      in: content,\n                      options: [],\n                      range: NSRange(content.startIndex..., in: content))\n            else {\n                continue\n            }\n\n            // The value is in capture group 1 for first pattern, group 1 for second pattern\n            let valueRange = match.range(at: 1)\n            if let range = Range(valueRange, in: content) {\n                return String(content[range])\n            }\n        }\n\n        return nil\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Kilo/KiloProviderDescriptor.swift",
    "content": "import CodexBarMacroSupport\nimport Foundation\n\n@ProviderDescriptorRegistration\n@ProviderDescriptorDefinition\npublic enum KiloProviderDescriptor {\n    static func makeDescriptor() -> ProviderDescriptor {\n        ProviderDescriptor(\n            id: .kilo,\n            metadata: ProviderMetadata(\n                id: .kilo,\n                displayName: \"Kilo\",\n                sessionLabel: \"Credits\",\n                weeklyLabel: \"Kilo Pass\",\n                opusLabel: nil,\n                supportsOpus: false,\n                supportsCredits: false,\n                creditsHint: \"\",\n                toggleTitle: \"Show Kilo usage\",\n                cliName: \"kilo\",\n                defaultEnabled: false,\n                isPrimaryProvider: false,\n                usesAccountFallback: false,\n                browserCookieOrder: nil,\n                dashboardURL: \"https://app.kilo.ai/usage\",\n                statusPageURL: nil),\n            branding: ProviderBranding(\n                iconStyle: .kilo,\n                iconResourceName: \"ProviderIcon-kilo\",\n                color: ProviderColor(red: 242 / 255, green: 112 / 255, blue: 39 / 255)),\n            tokenCost: ProviderTokenCostConfig(\n                supportsTokenCost: false,\n                noDataMessage: { \"Kilo cost summary is not supported.\" }),\n            fetchPlan: ProviderFetchPlan(\n                sourceModes: [.auto, .api, .cli],\n                pipeline: ProviderFetchPipeline(resolveStrategies: self.resolveStrategies)),\n            cli: ProviderCLIConfig(\n                name: \"kilo\",\n                aliases: [\"kilo-ai\"],\n                versionDetector: nil))\n    }\n\n    private static func resolveStrategies(context: ProviderFetchContext) async -> [any ProviderFetchStrategy] {\n        switch context.sourceMode {\n        case .api:\n            [KiloAPIFetchStrategy()]\n        case .cli:\n            [KiloCLIFetchStrategy()]\n        case .auto:\n            [KiloAPIFetchStrategy(), KiloCLIFetchStrategy()]\n        case .web, .oauth:\n            []\n        }\n    }\n}\n\nstruct KiloAPIFetchStrategy: ProviderFetchStrategy {\n    let id: String = \"kilo.api\"\n    let kind: ProviderFetchKind = .apiToken\n\n    func isAvailable(_ context: ProviderFetchContext) async -> Bool {\n        _ = context\n        // Keep strategy available so missing credentials surface as KiloUsageError.missingCredentials\n        // instead of generic ProviderFetchError.noAvailableStrategy.\n        return true\n    }\n\n    func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {\n        guard let apiKey = Self.resolveToken(environment: context.env) else {\n            throw KiloUsageError.missingCredentials\n        }\n        let usage = try await KiloUsageFetcher.fetchUsage(apiKey: apiKey, environment: context.env)\n        return self.makeResult(\n            usage: usage.toUsageSnapshot(),\n            sourceLabel: \"api\")\n    }\n\n    func shouldFallback(on error: Error, context: ProviderFetchContext) -> Bool {\n        guard context.sourceMode == .auto else { return false }\n        guard let kiloError = error as? KiloUsageError else { return false }\n        return kiloError == .missingCredentials || kiloError == .unauthorized\n    }\n\n    private static func resolveToken(environment: [String: String]) -> String? {\n        KiloSettingsReader.apiKey(environment: environment)\n    }\n}\n\nstruct KiloCLIFetchStrategy: ProviderFetchStrategy {\n    let id: String = \"kilo.cli\"\n    let kind: ProviderFetchKind = .cli\n\n    func isAvailable(_ context: ProviderFetchContext) async -> Bool {\n        _ = context\n        // Keep strategy available so CLI-specific session failures are surfaced as actionable errors.\n        return true\n    }\n\n    func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {\n        let token = try Self.resolveToken(environment: context.env)\n        let usage = try await KiloUsageFetcher.fetchUsage(apiKey: token, environment: context.env)\n        return self.makeResult(\n            usage: usage.toUsageSnapshot(),\n            sourceLabel: \"cli\")\n    }\n\n    func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool {\n        false\n    }\n\n    private static func resolveToken(environment: [String: String]) throws -> String {\n        let authFileURL = Self.authFileURL(environment: environment)\n        let fileManager = FileManager.default\n\n        guard fileManager.fileExists(atPath: authFileURL.path) else {\n            throw KiloUsageError.cliSessionMissing(authFileURL.path)\n        }\n\n        let data: Data\n        do {\n            data = try Data(contentsOf: authFileURL)\n        } catch {\n            throw KiloUsageError.cliSessionUnreadable(authFileURL.path)\n        }\n\n        guard let token = KiloSettingsReader.parseAuthToken(data: data) else {\n            throw KiloUsageError.cliSessionInvalid(authFileURL.path)\n        }\n\n        return token\n    }\n\n    private static func authFileURL(environment: [String: String]) -> URL {\n        if let home = KiloSettingsReader.cleaned(environment[\"HOME\"]) {\n            let expandedHome = NSString(string: home).expandingTildeInPath\n            return KiloSettingsReader.defaultAuthFileURL(\n                homeDirectory: URL(fileURLWithPath: expandedHome, isDirectory: true))\n        }\n        return KiloSettingsReader.defaultAuthFileURL(\n            homeDirectory: FileManager.default.homeDirectoryForCurrentUser)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Kilo/KiloSettingsReader.swift",
    "content": "import Foundation\n\npublic enum KiloSettingsReader {\n    public static let apiTokenKey = \"KILO_API_KEY\"\n\n    public static func apiKey(environment: [String: String] = ProcessInfo.processInfo.environment) -> String? {\n        self.cleaned(environment[self.apiTokenKey])\n    }\n\n    public static func apiURL(environment: [String: String] = ProcessInfo.processInfo.environment) -> URL {\n        _ = environment\n        return URL(string: \"https://app.kilo.ai/api/trpc\")!\n    }\n\n    public static func authToken(\n        authFileURL: URL? = nil,\n        homeDirectory: URL = FileManager.default.homeDirectoryForCurrentUser) -> String?\n    {\n        let fileURL = authFileURL ?? self.defaultAuthFileURL(homeDirectory: homeDirectory)\n        guard let data = try? Data(contentsOf: fileURL) else { return nil }\n        return self.parseAuthToken(data: data)\n    }\n\n    static func defaultAuthFileURL(homeDirectory: URL) -> URL {\n        homeDirectory\n            .appendingPathComponent(\".local\", isDirectory: true)\n            .appendingPathComponent(\"share\", isDirectory: true)\n            .appendingPathComponent(\"kilo\", isDirectory: true)\n            .appendingPathComponent(\"auth.json\", isDirectory: false)\n    }\n\n    static func parseAuthToken(data: Data) -> String? {\n        guard let payload = try? JSONDecoder().decode(AuthFile.self, from: data) else {\n            return nil\n        }\n        return self.cleaned(payload.kilo?.access)\n    }\n\n    static func cleaned(_ raw: String?) -> String? {\n        guard var value = raw?.trimmingCharacters(in: .whitespacesAndNewlines), !value.isEmpty else {\n            return nil\n        }\n\n        if (value.hasPrefix(\"\\\"\") && value.hasSuffix(\"\\\"\")) ||\n            (value.hasPrefix(\"'\") && value.hasSuffix(\"'\"))\n        {\n            value.removeFirst()\n            value.removeLast()\n        }\n\n        value = value.trimmingCharacters(in: .whitespacesAndNewlines)\n        return value.isEmpty ? nil : value\n    }\n}\n\nprivate struct AuthFile: Decodable {\n    let kilo: KiloSection?\n\n    struct KiloSection: Decodable {\n        let access: String?\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Kilo/KiloUsageDataSource.swift",
    "content": "import Foundation\n\npublic enum KiloUsageDataSource: String, CaseIterable, Identifiable, Sendable {\n    case auto\n    case api\n    case cli\n\n    public var id: String {\n        self.rawValue\n    }\n\n    public var displayName: String {\n        switch self {\n        case .auto: \"Auto\"\n        case .api: \"API\"\n        case .cli: \"CLI\"\n        }\n    }\n\n    public var sourceLabel: String {\n        switch self {\n        case .auto:\n            \"auto\"\n        case .api:\n            \"api\"\n        case .cli:\n            \"cli\"\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Kilo/KiloUsageFetcher.swift",
    "content": "import Foundation\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n\npublic struct KiloUsageSnapshot: Sendable {\n    public let creditsUsed: Double?\n    public let creditsTotal: Double?\n    public let creditsRemaining: Double?\n    public let passUsed: Double?\n    public let passTotal: Double?\n    public let passRemaining: Double?\n    public let passBonus: Double?\n    public let passResetsAt: Date?\n    public let planName: String?\n    public let autoTopUpEnabled: Bool?\n    public let autoTopUpMethod: String?\n    public let updatedAt: Date\n\n    public init(\n        creditsUsed: Double?,\n        creditsTotal: Double?,\n        creditsRemaining: Double?,\n        passUsed: Double? = nil,\n        passTotal: Double? = nil,\n        passRemaining: Double? = nil,\n        passBonus: Double? = nil,\n        passResetsAt: Date? = nil,\n        planName: String?,\n        autoTopUpEnabled: Bool?,\n        autoTopUpMethod: String?,\n        updatedAt: Date)\n    {\n        self.creditsUsed = creditsUsed\n        self.creditsTotal = creditsTotal\n        self.creditsRemaining = creditsRemaining\n        self.passUsed = passUsed\n        self.passTotal = passTotal\n        self.passRemaining = passRemaining\n        self.passBonus = passBonus\n        self.passResetsAt = passResetsAt\n        self.planName = planName\n        self.autoTopUpEnabled = autoTopUpEnabled\n        self.autoTopUpMethod = autoTopUpMethod\n        self.updatedAt = updatedAt\n    }\n\n    public func toUsageSnapshot() -> UsageSnapshot {\n        let total = self.resolvedTotal\n        let used = self.resolvedUsed\n\n        let primary: RateWindow?\n        if let total {\n            let usedPercent: Double = if total > 0 {\n                min(100, max(0, (used / total) * 100))\n            } else {\n                // Preserve a visible exhausted state for valid zero-total snapshots.\n                100\n            }\n            let usedText = Self.compactNumber(used)\n            let totalText = Self.compactNumber(total)\n            primary = RateWindow(\n                usedPercent: usedPercent,\n                windowMinutes: nil,\n                resetsAt: nil,\n                resetDescription: \"\\(usedText)/\\(totalText) credits\")\n        } else {\n            primary = nil\n        }\n\n        let loginMethod = Self.makeLoginMethod(\n            planName: self.planName,\n            autoTopUpEnabled: self.autoTopUpEnabled,\n            autoTopUpMethod: self.autoTopUpMethod)\n\n        return UsageSnapshot(\n            primary: primary,\n            secondary: self.passWindow,\n            tertiary: nil,\n            providerCost: nil,\n            updatedAt: self.updatedAt,\n            identity: ProviderIdentitySnapshot(\n                providerID: .kilo,\n                accountEmail: nil,\n                accountOrganization: nil,\n                loginMethod: loginMethod))\n    }\n\n    private var resolvedTotal: Double? {\n        if let creditsTotal { return max(0, creditsTotal) }\n        if let creditsUsed, let creditsRemaining {\n            return max(0, creditsUsed + creditsRemaining)\n        }\n        return nil\n    }\n\n    private var resolvedUsed: Double {\n        if let creditsUsed {\n            return max(0, creditsUsed)\n        }\n        if let total = self.resolvedTotal,\n           let creditsRemaining\n        {\n            return max(0, total - creditsRemaining)\n        }\n        return 0\n    }\n\n    private var resolvedPassTotal: Double? {\n        if let passTotal { return max(0, passTotal) }\n        if let passUsed, let passRemaining {\n            return max(0, passUsed + passRemaining)\n        }\n        return nil\n    }\n\n    private var resolvedPassUsed: Double {\n        if let passUsed {\n            return max(0, passUsed)\n        }\n        if let total = self.resolvedPassTotal,\n           let passRemaining\n        {\n            return max(0, total - passRemaining)\n        }\n        return 0\n    }\n\n    private var passWindow: RateWindow? {\n        guard let total = self.resolvedPassTotal else {\n            return nil\n        }\n\n        let used = self.resolvedPassUsed\n        let bonus = max(0, self.passBonus ?? 0)\n        let baseCredits = max(0, total - bonus)\n        let usedPercent: Double = if total > 0 {\n            min(100, max(0, (used / total) * 100))\n        } else {\n            100\n        }\n\n        var detail = \"$\\(Self.currencyNumber(used)) / $\\(Self.currencyNumber(baseCredits))\"\n        if bonus > 0 {\n            detail += \" (+ $\\(Self.currencyNumber(bonus)) bonus)\"\n        }\n\n        return RateWindow(\n            usedPercent: usedPercent,\n            windowMinutes: nil,\n            resetsAt: self.passResetsAt,\n            resetDescription: detail)\n    }\n\n    private static func compactNumber(_ value: Double) -> String {\n        if value.rounded(.towardZero) == value {\n            return String(Int(value))\n        }\n        return String(format: \"%.2f\", value)\n    }\n\n    private static func currencyNumber(_ value: Double) -> String {\n        String(format: \"%.2f\", max(0, value))\n    }\n\n    private static func makeLoginMethod(\n        planName: String?,\n        autoTopUpEnabled: Bool?,\n        autoTopUpMethod: String?) -> String?\n    {\n        var parts: [String] = []\n\n        if let planName = Self.trimmed(planName) {\n            parts.append(planName)\n        }\n\n        if let autoTopUpEnabled {\n            if autoTopUpEnabled {\n                if let method = Self.trimmed(autoTopUpMethod) {\n                    parts.append(\"Auto top-up: \\(method)\")\n                } else {\n                    parts.append(\"Auto top-up: enabled\")\n                }\n            } else {\n                parts.append(\"Auto top-up: off\")\n            }\n        }\n\n        return parts.isEmpty ? nil : parts.joined(separator: \" · \")\n    }\n\n    private static func trimmed(_ raw: String?) -> String? {\n        guard let raw else { return nil }\n        let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)\n        return trimmed.isEmpty ? nil : trimmed\n    }\n}\n\npublic enum KiloUsageError: LocalizedError, Sendable, Equatable {\n    case missingCredentials\n    case cliSessionMissing(String)\n    case cliSessionUnreadable(String)\n    case cliSessionInvalid(String)\n    case unauthorized\n    case endpointNotFound\n    case serviceUnavailable(Int)\n    case networkError(String)\n    case parseFailed(String)\n    case apiError(Int)\n\n    public var errorDescription: String? {\n        switch self {\n        case .missingCredentials:\n            \"Kilo API credentials missing. Set KILO_API_KEY.\"\n        case let .cliSessionMissing(path):\n            \"Kilo CLI session not found at \\(path). Run `kilo login` to create ~/.local/share/kilo/auth.json.\"\n        case let .cliSessionUnreadable(path):\n            \"Kilo CLI session file is unreadable at \\(path). Fix permissions or run `kilo login` again.\"\n        case let .cliSessionInvalid(path):\n            \"Kilo CLI session file is invalid at \\(path). Run `kilo login` to refresh auth.json.\"\n        case .unauthorized:\n            \"Kilo authentication failed (401/403). Refresh KILO_API_KEY or run `kilo login`.\"\n        case .endpointNotFound:\n            \"Kilo API endpoint not found (404). Verify the tRPC batch path and procedure names.\"\n        case let .serviceUnavailable(statusCode):\n            \"Kilo API is currently unavailable (HTTP \\(statusCode)). Try again later.\"\n        case let .networkError(message):\n            \"Kilo network error: \\(message)\"\n        case .parseFailed:\n            \"Failed to parse Kilo API response. Response format may have changed.\"\n        case let .apiError(statusCode):\n            \"Kilo API request failed (HTTP \\(statusCode)).\"\n        }\n    }\n}\n\npublic struct KiloUsageFetcher: Sendable {\n    private struct KiloPassFields {\n        let used: Double?\n        let total: Double?\n        let remaining: Double?\n        let bonus: Double?\n        let resetsAt: Date?\n    }\n\n    static let procedures = [\n        \"user.getCreditBlocks\",\n        \"kiloPass.getState\",\n        \"user.getAutoTopUpPaymentMethod\",\n    ]\n\n    private static let optionalProcedures: Set<String> = [\n        \"user.getAutoTopUpPaymentMethod\",\n    ]\n\n    private static let maxTopLevelEntries = procedures.count\n\n    public static func fetchUsage(\n        apiKey: String,\n        environment: [String: String] = ProcessInfo.processInfo.environment) async throws -> KiloUsageSnapshot\n    {\n        guard !apiKey.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {\n            throw KiloUsageError.missingCredentials\n        }\n\n        let baseURL = KiloSettingsReader.apiURL(environment: environment)\n        let batchURL = try self.makeBatchURL(baseURL: baseURL)\n\n        var request = URLRequest(url: batchURL)\n        request.httpMethod = \"GET\"\n        request.timeoutInterval = 15\n        request.setValue(\"Bearer \\(apiKey)\", forHTTPHeaderField: \"Authorization\")\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Accept\")\n\n        let data: Data\n        let response: URLResponse\n        do {\n            (data, response) = try await URLSession.shared.data(for: request)\n        } catch {\n            throw KiloUsageError.networkError(error.localizedDescription)\n        }\n\n        guard let httpResponse = response as? HTTPURLResponse else {\n            throw KiloUsageError.networkError(\"Invalid response\")\n        }\n\n        if let mapped = self.statusError(for: httpResponse.statusCode) {\n            throw mapped\n        }\n\n        guard httpResponse.statusCode == 200 else {\n            throw KiloUsageError.apiError(httpResponse.statusCode)\n        }\n\n        return try self.parseSnapshot(data: data)\n    }\n\n    static func _buildBatchURLForTesting(baseURL: URL) throws -> URL {\n        try self.makeBatchURL(baseURL: baseURL)\n    }\n\n    static func _parseSnapshotForTesting(_ data: Data) throws -> KiloUsageSnapshot {\n        try self.parseSnapshot(data: data)\n    }\n\n    static func _statusErrorForTesting(_ statusCode: Int) -> KiloUsageError? {\n        self.statusError(for: statusCode)\n    }\n\n    private static func statusError(for statusCode: Int) -> KiloUsageError? {\n        switch statusCode {\n        case 401, 403:\n            .unauthorized\n        case 404:\n            .endpointNotFound\n        case 500...599:\n            .serviceUnavailable(statusCode)\n        default:\n            nil\n        }\n    }\n\n    private static func makeBatchURL(baseURL: URL) throws -> URL {\n        let joinedProcedures = self.procedures.joined(separator: \",\")\n        let endpoint = baseURL.appendingPathComponent(joinedProcedures)\n\n        let inputMap = Dictionary(uniqueKeysWithValues: self.procedures.indices.map {\n            (String($0), [\"json\": NSNull()])\n        })\n        let inputData = try JSONSerialization.data(withJSONObject: inputMap)\n        guard let inputString = String(data: inputData, encoding: .utf8) else {\n            throw KiloUsageError.parseFailed(\"Invalid batch input\")\n        }\n\n        guard var components = URLComponents(url: endpoint, resolvingAgainstBaseURL: false) else {\n            throw KiloUsageError.parseFailed(\"Invalid batch endpoint\")\n        }\n        components.queryItems = [\n            URLQueryItem(name: \"batch\", value: \"1\"),\n            URLQueryItem(name: \"input\", value: inputString),\n        ]\n\n        guard let url = components.url else {\n            throw KiloUsageError.parseFailed(\"Invalid batch endpoint\")\n        }\n        return url\n    }\n\n    private static func parseSnapshot(data: Data) throws -> KiloUsageSnapshot {\n        guard let root = try? JSONSerialization.jsonObject(with: data) else {\n            throw KiloUsageError.parseFailed(\"Invalid JSON\")\n        }\n\n        let entriesByIndex = try self.responseEntriesByIndex(from: root)\n        var payloadsByProcedure: [String: Any] = [:]\n\n        for (index, procedure) in self.procedures.enumerated() {\n            guard let entry = entriesByIndex[index] else { continue }\n            if let mappedError = self.trpcError(from: entry) {\n                guard self.isRequiredProcedure(procedure) else {\n                    continue\n                }\n                throw mappedError\n            }\n            if let payload = self.resultPayload(from: entry) {\n                payloadsByProcedure[procedure] = payload\n            }\n        }\n\n        let creditFields = self.creditFields(from: payloadsByProcedure[self.procedures[0]])\n        let passFields = self.passFields(from: payloadsByProcedure[self.procedures[1]])\n        let planName = self.planName(from: payloadsByProcedure[self.procedures[1]])\n        let autoTopUp = self.autoTopUpState(\n            creditBlocksPayload: payloadsByProcedure[self.procedures[0]],\n            autoTopUpPayload: payloadsByProcedure[self.procedures[2]])\n\n        return KiloUsageSnapshot(\n            creditsUsed: creditFields.used,\n            creditsTotal: creditFields.total,\n            creditsRemaining: creditFields.remaining,\n            passUsed: passFields.used,\n            passTotal: passFields.total,\n            passRemaining: passFields.remaining,\n            passBonus: passFields.bonus,\n            passResetsAt: passFields.resetsAt,\n            planName: planName,\n            autoTopUpEnabled: autoTopUp.enabled,\n            autoTopUpMethod: autoTopUp.method,\n            updatedAt: Date())\n    }\n\n    private static func isRequiredProcedure(_ procedure: String) -> Bool {\n        !self.optionalProcedures.contains(procedure)\n    }\n\n    private static func responseEntriesByIndex(from root: Any) throws -> [Int: [String: Any]] {\n        if let entries = root as? [[String: Any]] {\n            let limited = Array(entries.prefix(self.maxTopLevelEntries))\n            return Dictionary(uniqueKeysWithValues: limited.enumerated().map { ($0.offset, $0.element) })\n        }\n\n        if let dictionary = root as? [String: Any] {\n            if dictionary[\"result\"] != nil || dictionary[\"error\"] != nil {\n                return [0: dictionary]\n            }\n\n            let indexedEntries = dictionary\n                .compactMap { key, value -> (Int, [String: Any])? in\n                    guard let index = Int(key),\n                          let entry = value as? [String: Any]\n                    else {\n                        return nil\n                    }\n                    return (index, entry)\n                }\n            if !indexedEntries.isEmpty {\n                let limitedEntries = indexedEntries.filter { $0.0 >= 0 && $0.0 < self.maxTopLevelEntries }\n                return Dictionary(uniqueKeysWithValues: limitedEntries)\n            }\n        }\n\n        throw KiloUsageError.parseFailed(\"Unexpected tRPC batch shape\")\n    }\n\n    private static func trpcError(from entry: [String: Any]) -> KiloUsageError? {\n        guard let errorObject = entry[\"error\"] as? [String: Any] else { return nil }\n\n        let code = self.stringValue(for: [\"json\", \"data\", \"code\"], in: errorObject)\n            ?? self.stringValue(for: [\"data\", \"code\"], in: errorObject)\n            ?? self.stringValue(for: [\"code\"], in: errorObject)\n        let message = self.stringValue(for: [\"json\", \"message\"], in: errorObject)\n            ?? self.stringValue(for: [\"message\"], in: errorObject)\n\n        let combined = [code, message]\n            .compactMap { $0?.lowercased() }\n            .joined(separator: \" \")\n\n        if combined.contains(\"unauthorized\") || combined.contains(\"forbidden\") {\n            return .unauthorized\n        }\n\n        if combined.contains(\"not_found\") || combined.contains(\"not found\") {\n            return .endpointNotFound\n        }\n\n        return .parseFailed(\"tRPC error payload\")\n    }\n\n    private static func resultPayload(from entry: [String: Any]) -> Any? {\n        guard let resultObject = entry[\"result\"] as? [String: Any] else { return nil }\n\n        if let dataObject = resultObject[\"data\"] as? [String: Any] {\n            if let jsonPayload = dataObject[\"json\"] {\n                if jsonPayload is NSNull { return nil }\n                return jsonPayload\n            }\n            return dataObject\n        }\n\n        if let jsonPayload = resultObject[\"json\"] {\n            if jsonPayload is NSNull { return nil }\n            return jsonPayload\n        }\n\n        return nil\n    }\n\n    private static func creditFields(from payload: Any?) -> (used: Double?, total: Double?, remaining: Double?) {\n        guard let payload else { return (nil, nil, nil) }\n\n        let contexts = self.dictionaryContexts(from: payload)\n        let blocks = self.firstArray(forKeys: [\"creditBlocks\"], in: contexts)\n\n        if let blocks {\n            var totalFromBlocks: Double = 0\n            var remainingFromBlocks: Double = 0\n            var sawTotal = false\n            var sawRemaining = false\n\n            for case let block as [String: Any] in blocks {\n                if let amountMicroUSD = self.double(from: block[\"amount_mUsd\"]) {\n                    totalFromBlocks += amountMicroUSD / 1_000_000\n                    sawTotal = true\n                }\n                if let balanceMicroUSD = self.double(from: block[\"balance_mUsd\"]) {\n                    remainingFromBlocks += balanceMicroUSD / 1_000_000\n                    sawRemaining = true\n                }\n            }\n\n            if sawTotal || sawRemaining {\n                let total = sawTotal ? max(0, totalFromBlocks) : nil\n                let remaining = sawRemaining ? max(0, remainingFromBlocks) : nil\n                let used: Double? = if let total, let remaining {\n                    max(0, total - remaining)\n                } else {\n                    nil\n                }\n                return (used, total, remaining)\n            }\n        }\n\n        let genericBlocks = self.firstArray(forKeys: [\"blocks\"], in: contexts)\n        let blockContexts = (genericBlocks ?? []).compactMap { $0 as? [String: Any] }\n\n        var used = self.firstDouble(\n            forKeys: [\"used\", \"usedCredits\", \"consumed\", \"spent\", \"creditsUsed\"],\n            in: blockContexts)\n        var total = self.firstDouble(forKeys: [\"total\", \"totalCredits\", \"creditsTotal\", \"limit\"], in: blockContexts)\n        var remaining = self.firstDouble(\n            forKeys: [\"remaining\", \"remainingCredits\", \"creditsRemaining\"],\n            in: blockContexts)\n\n        if used == nil {\n            used = self.firstDouble(\n                forKeys: [\"used\", \"usedCredits\", \"creditsUsed\", \"consumed\", \"spent\"],\n                in: contexts)\n        }\n        if total == nil {\n            total = self.firstDouble(forKeys: [\"total\", \"totalCredits\", \"creditsTotal\", \"limit\"], in: contexts)\n        }\n        if remaining == nil {\n            remaining = self.firstDouble(\n                forKeys: [\"remaining\", \"remainingCredits\", \"creditsRemaining\"],\n                in: contexts)\n        }\n\n        if total == nil,\n           let used,\n           let remaining\n        {\n            total = used + remaining\n        }\n\n        if used == nil, total == nil, remaining == nil,\n           let balanceMilliUSD = self.firstDouble(forKeys: [\"totalBalance_mUsd\"], in: contexts),\n           balanceMilliUSD == 0\n        {\n            // Kilo may return an empty creditBlocks list for zero-balance accounts.\n            // Keep this visible as an explicit exhausted edge state instead of \"no data\".\n            return (0, 0, 0)\n        }\n\n        if used == nil,\n           total == nil,\n           remaining == nil,\n           let balanceMilliUSD = self.firstDouble(forKeys: [\"totalBalance_mUsd\"], in: contexts)\n        {\n            let balance = max(0, balanceMilliUSD / 1_000_000)\n            return (max(0, 0), balance, balance)\n        }\n\n        return (used, total, remaining)\n    }\n\n    private static func passFields(from payload: Any?) -> KiloPassFields {\n        if let subscription = self.subscriptionData(from: payload) {\n            let used = self.double(from: subscription[\"currentPeriodUsageUsd\"]).map { max(0, $0) }\n            let baseCredits = self.double(from: subscription[\"currentPeriodBaseCreditsUsd\"]).map { max(0, $0) }\n            let bonusCredits = max(0, self.double(from: subscription[\"currentPeriodBonusCreditsUsd\"]) ?? 0)\n            let total = baseCredits.map { $0 + bonusCredits }\n            let remaining: Double? = if let total, let used {\n                max(0, total - used)\n            } else {\n                nil\n            }\n            let resetsAt = self.date(from: subscription[\"nextBillingAt\"])\n                ?? self.date(from: subscription[\"nextRenewalAt\"])\n                ?? self.date(from: subscription[\"renewsAt\"])\n                ?? self.date(from: subscription[\"renewAt\"])\n\n            return KiloPassFields(\n                used: used,\n                total: total,\n                remaining: remaining,\n                bonus: bonusCredits > 0 ? bonusCredits : nil,\n                resetsAt: resetsAt)\n        }\n\n        return self.fallbackPassFields(from: payload)\n    }\n\n    private static func fallbackPassFields(from payload: Any?) -> KiloPassFields {\n        let contexts = self.dictionaryContexts(from: payload)\n        guard !contexts.isEmpty else {\n            return KiloPassFields(used: nil, total: nil, remaining: nil, bonus: nil, resetsAt: nil)\n        }\n\n        var total = self.moneyAmount(\n            centsKeys: [\n                \"amountCents\",\n                \"totalCents\",\n                \"planAmountCents\",\n                \"monthlyAmountCents\",\n                \"limitCents\",\n                \"includedCents\",\n                \"valueCents\",\n            ],\n            milliUSDKeys: [\n                \"amount_mUsd\",\n                \"total_mUsd\",\n                \"planAmount_mUsd\",\n                \"limit_mUsd\",\n                \"included_mUsd\",\n                \"value_mUsd\",\n            ],\n            plainKeys: [\n                \"amount\",\n                \"total\",\n                \"limit\",\n                \"included\",\n                \"value\",\n                \"creditsTotal\",\n                \"totalCredits\",\n                \"planAmount\",\n            ],\n            in: contexts)\n        var used = self.moneyAmount(\n            centsKeys: [\n                \"usedCents\",\n                \"spentCents\",\n                \"consumedCents\",\n                \"usedAmountCents\",\n                \"consumedAmountCents\",\n            ],\n            milliUSDKeys: [\n                \"used_mUsd\",\n                \"spent_mUsd\",\n                \"consumed_mUsd\",\n                \"usedAmount_mUsd\",\n            ],\n            plainKeys: [\n                \"used\",\n                \"spent\",\n                \"consumed\",\n                \"usage\",\n                \"creditsUsed\",\n                \"usedAmount\",\n                \"consumedAmount\",\n            ],\n            in: contexts)\n        var remaining = self.moneyAmount(\n            centsKeys: [\n                \"remainingCents\",\n                \"remainingAmountCents\",\n                \"availableCents\",\n                \"leftCents\",\n                \"balanceCents\",\n            ],\n            milliUSDKeys: [\n                \"remaining_mUsd\",\n                \"available_mUsd\",\n                \"left_mUsd\",\n                \"balance_mUsd\",\n            ],\n            plainKeys: [\n                \"remaining\",\n                \"available\",\n                \"left\",\n                \"balance\",\n                \"creditsRemaining\",\n                \"remainingAmount\",\n                \"availableAmount\",\n            ],\n            in: contexts)\n        let bonus = self.moneyAmount(\n            centsKeys: [\n                \"bonusCents\",\n                \"bonusAmountCents\",\n                \"includedBonusCents\",\n                \"bonusRemainingCents\",\n            ],\n            milliUSDKeys: [\n                \"bonus_mUsd\",\n                \"bonusAmount_mUsd\",\n            ],\n            plainKeys: [\n                \"bonus\",\n                \"bonusAmount\",\n                \"bonusCredits\",\n                \"includedBonus\",\n            ],\n            in: contexts)\n        let resetsAt = self.firstDate(\n            forKeys: [\n                \"resetAt\",\n                \"resetsAt\",\n                \"nextResetAt\",\n                \"renewAt\",\n                \"renewsAt\",\n                \"nextRenewalAt\",\n                \"currentPeriodEnd\",\n                \"periodEndsAt\",\n                \"expiresAt\",\n                \"expiryAt\",\n            ],\n            in: contexts)\n\n        if total == nil,\n           let used,\n           let remaining\n        {\n            total = used + remaining\n        }\n        if used == nil,\n           let total,\n           let remaining\n        {\n            used = max(0, total - remaining)\n        }\n        if remaining == nil,\n           let total,\n           let used\n        {\n            remaining = max(0, total - used)\n        }\n\n        return KiloPassFields(\n            used: used,\n            total: total,\n            remaining: remaining,\n            bonus: bonus,\n            resetsAt: resetsAt)\n    }\n\n    private static func planName(from payload: Any?) -> String? {\n        if let subscription = self.subscriptionData(from: payload) {\n            if let tier = self.string(from: subscription[\"tier\"]) {\n                let trimmed = tier.trimmingCharacters(in: .whitespacesAndNewlines)\n                if !trimmed.isEmpty {\n                    return self.planNameForTier(trimmed)\n                }\n            }\n            return \"Kilo Pass\"\n        }\n\n        let contexts = self.dictionaryContexts(from: payload)\n        let candidates = [\n            self.firstString(\n                forKeys: [\"planName\", \"tier\", \"tierName\", \"passName\", \"subscriptionName\"],\n                in: contexts),\n            self.stringValue(for: [\"plan\", \"name\"], in: contexts),\n            self.stringValue(for: [\"subscription\", \"plan\", \"name\"], in: contexts),\n            self.stringValue(for: [\"subscription\", \"name\"], in: contexts),\n            self.stringValue(for: [\"pass\", \"name\"], in: contexts),\n            self.stringValue(for: [\"state\", \"name\"], in: contexts),\n            self.stringValue(for: [\"state\"], in: contexts),\n        ]\n\n        for candidate in candidates {\n            guard let trimmed = candidate?.trimmingCharacters(in: .whitespacesAndNewlines), !trimmed.isEmpty else {\n                continue\n            }\n            return trimmed\n        }\n        if let fallback = self.firstString(forKeys: [\"name\"], in: contexts),\n           fallback.lowercased().contains(\"pass\")\n        {\n            return fallback\n        }\n        return nil\n    }\n\n    private static func autoTopUpState(\n        creditBlocksPayload: Any?,\n        autoTopUpPayload: Any?) -> (enabled: Bool?, method: String?)\n    {\n        let creditContexts = self.dictionaryContexts(from: creditBlocksPayload)\n        let autoTopUpContexts = self.dictionaryContexts(from: autoTopUpPayload)\n        let enabled = self.firstBool(forKeys: [\"enabled\", \"isEnabled\", \"active\"], in: autoTopUpContexts)\n            ?? self.boolFromStatusString(self.firstString(forKeys: [\"status\"], in: autoTopUpContexts))\n            ?? self.firstBool(forKeys: [\"autoTopUpEnabled\"], in: creditContexts)\n\n        let rawMethod = self.firstString(\n            forKeys: [\"paymentMethod\", \"paymentMethodType\", \"method\", \"cardBrand\"],\n            in: autoTopUpContexts)?.trimmingCharacters(in: .whitespacesAndNewlines)\n        let amount = self.moneyAmount(\n            centsKeys: [\"amountCents\"],\n            milliUSDKeys: [],\n            plainKeys: [\"amount\", \"topUpAmount\", \"amountUsd\"],\n            in: autoTopUpContexts)\n\n        let method: String? = if let rawMethod, !rawMethod.isEmpty {\n            rawMethod\n        } else if let amount, amount > 0 {\n            self.currencyAmountLabel(amount)\n        } else {\n            nil\n        }\n\n        return (enabled, method)\n    }\n\n    private static func subscriptionData(from payload: Any?) -> [String: Any]? {\n        guard let payloadDictionary = payload as? [String: Any] else {\n            return nil\n        }\n\n        if let subscription = payloadDictionary[\"subscription\"] as? [String: Any] {\n            return subscription\n        }\n\n        if payloadDictionary[\"subscription\"] is NSNull {\n            return nil\n        }\n\n        let hasSubscriptionShape = payloadDictionary[\"currentPeriodUsageUsd\"] != nil ||\n            payloadDictionary[\"currentPeriodBaseCreditsUsd\"] != nil ||\n            payloadDictionary[\"currentPeriodBonusCreditsUsd\"] != nil ||\n            payloadDictionary[\"tier\"] != nil\n        return hasSubscriptionShape ? payloadDictionary : nil\n    }\n\n    private static func planNameForTier(_ tier: String) -> String {\n        switch tier {\n        case \"tier_19\":\n            \"Starter\"\n        case \"tier_49\":\n            \"Pro\"\n        case \"tier_199\":\n            \"Expert\"\n        default:\n            tier\n        }\n    }\n\n    private static func string(from raw: Any?) -> String? {\n        if let value = raw as? String {\n            return value\n        }\n        return nil\n    }\n\n    private static func dictionaryContexts(from payload: Any?) -> [[String: Any]] {\n        guard let payload else { return [] }\n        guard let dictionary = payload as? [String: Any] else { return [] }\n\n        var contexts: [[String: Any]] = []\n        var queue: [([String: Any], Int)] = [(dictionary, 0)]\n        let maxDepth = 2\n\n        while !queue.isEmpty {\n            let (current, depth) = queue.removeFirst()\n            contexts.append(current)\n\n            guard depth < maxDepth else {\n                continue\n            }\n\n            for value in current.values {\n                if let nested = value as? [String: Any] {\n                    queue.append((nested, depth + 1))\n                    continue\n                }\n                if let nestedArray = value as? [Any] {\n                    for case let nested as [String: Any] in nestedArray {\n                        queue.append((nested, depth + 1))\n                    }\n                }\n            }\n        }\n\n        return contexts\n    }\n\n    private static func firstArray(forKeys keys: [String], in contexts: [[String: Any]]) -> [Any]? {\n        for context in contexts {\n            for key in keys {\n                if let values = context[key] as? [Any] {\n                    return values\n                }\n            }\n        }\n        return nil\n    }\n\n    private static func firstDouble(forKeys keys: [String], in contexts: [[String: Any]]) -> Double? {\n        for context in contexts {\n            for key in keys {\n                if let value = self.double(from: context[key]) {\n                    return value\n                }\n            }\n        }\n        return nil\n    }\n\n    private static func firstString(forKeys keys: [String], in contexts: [[String: Any]]) -> String? {\n        for context in contexts {\n            for key in keys {\n                if let value = context[key] as? String {\n                    return value\n                }\n            }\n        }\n        return nil\n    }\n\n    private static func firstBool(forKeys keys: [String], in contexts: [[String: Any]]) -> Bool? {\n        for context in contexts {\n            for key in keys {\n                if let value = self.bool(from: context[key]) {\n                    return value\n                }\n            }\n        }\n        return nil\n    }\n\n    private static func firstDate(forKeys keys: [String], in contexts: [[String: Any]]) -> Date? {\n        for context in contexts {\n            for key in keys {\n                if let value = self.date(from: context[key]) {\n                    return value\n                }\n            }\n        }\n        return nil\n    }\n\n    private static func moneyAmount(\n        centsKeys: [String],\n        milliUSDKeys: [String],\n        plainKeys: [String],\n        in contexts: [[String: Any]]) -> Double?\n    {\n        if let cents = self.firstDouble(forKeys: centsKeys, in: contexts) {\n            return cents / 100\n        }\n        if let milliUSD = self.firstDouble(forKeys: milliUSDKeys, in: contexts) {\n            return milliUSD / 1_000_000\n        }\n        return self.firstDouble(forKeys: plainKeys, in: contexts)\n    }\n\n    private static func currencyAmountLabel(_ amount: Double) -> String {\n        if amount.rounded(.towardZero) == amount {\n            return String(format: \"$%.0f\", amount)\n        }\n        return String(format: \"$%.2f\", amount)\n    }\n\n    private static func stringValue(for path: [String], in dictionary: [String: Any]) -> String? {\n        var cursor: Any = dictionary\n        for key in path {\n            guard let next = (cursor as? [String: Any])?[key] else {\n                return nil\n            }\n            cursor = next\n        }\n        return cursor as? String\n    }\n\n    private static func stringValue(for path: [String], in contexts: [[String: Any]]) -> String? {\n        for context in contexts {\n            if let value = self.stringValue(for: path, in: context) {\n                return value\n            }\n        }\n        return nil\n    }\n\n    private static func boolFromStatusString(_ status: String?) -> Bool? {\n        guard let status = status?.trimmingCharacters(in: .whitespacesAndNewlines).lowercased(),\n              !status.isEmpty\n        else {\n            return nil\n        }\n\n        switch status {\n        case \"enabled\", \"active\", \"on\":\n            return true\n        case \"disabled\", \"inactive\", \"off\", \"none\":\n            return false\n        default:\n            return nil\n        }\n    }\n\n    private static func double(from raw: Any?) -> Double? {\n        switch raw {\n        case let value as Double:\n            value\n        case let value as Int:\n            Double(value)\n        case let value as NSNumber:\n            value.doubleValue\n        case let value as String:\n            Double(value.trimmingCharacters(in: .whitespacesAndNewlines))\n        default:\n            nil\n        }\n    }\n\n    private static func date(from raw: Any?) -> Date? {\n        switch raw {\n        case let value as Date:\n            return value\n        case let value as Double:\n            return self.dateFromEpoch(value)\n        case let value as Int:\n            return self.dateFromEpoch(Double(value))\n        case let value as NSNumber:\n            return self.dateFromEpoch(value.doubleValue)\n        case let value as String:\n            let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)\n            guard !trimmed.isEmpty else { return nil }\n            if let numeric = Double(trimmed) {\n                return self.dateFromEpoch(numeric)\n            }\n\n            let withFractional = ISO8601DateFormatter()\n            withFractional.formatOptions = [.withInternetDateTime, .withFractionalSeconds]\n            if let parsed = withFractional.date(from: trimmed) {\n                return parsed\n            }\n\n            let plain = ISO8601DateFormatter()\n            plain.formatOptions = [.withInternetDateTime]\n            return plain.date(from: trimmed)\n        default:\n            return nil\n        }\n    }\n\n    private static func dateFromEpoch(_ value: Double) -> Date {\n        let seconds = abs(value) > 10_000_000_000 ? value / 1000 : value\n        return Date(timeIntervalSince1970: seconds)\n    }\n\n    private static func bool(from raw: Any?) -> Bool? {\n        switch raw {\n        case let value as Bool:\n            return value\n        case let value as NSNumber:\n            return value.boolValue\n        case let value as String:\n            let normalized = value.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()\n            if [\"true\", \"1\", \"yes\", \"enabled\", \"on\"].contains(normalized) {\n                return true\n            }\n            if [\"false\", \"0\", \"no\", \"disabled\", \"off\"].contains(normalized) {\n                return false\n            }\n            return nil\n        default:\n            return nil\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Kimi/KimiAPIError.swift",
    "content": "import Foundation\n\npublic enum KimiAPIError: LocalizedError, Sendable, Equatable {\n    case missingToken\n    case invalidToken\n    case invalidRequest(String)\n    case networkError(String)\n    case apiError(String)\n    case parseFailed(String)\n\n    public var errorDescription: String? {\n        switch self {\n        case .missingToken:\n            \"Kimi auth token is missing. Please add your JWT token from the Kimi console.\"\n        case .invalidToken:\n            \"Kimi auth token is invalid or expired. Please refresh your token.\"\n        case let .invalidRequest(message):\n            \"Invalid request: \\(message)\"\n        case let .networkError(message):\n            \"Kimi network error: \\(message)\"\n        case let .apiError(message):\n            \"Kimi API error: \\(message)\"\n        case let .parseFailed(message):\n            \"Failed to parse Kimi usage data: \\(message)\"\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Kimi/KimiCookieHeader.swift",
    "content": "import Foundation\n\npublic struct KimiCookieOverride: Sendable {\n    public let token: String\n\n    public init(token: String) {\n        self.token = token\n    }\n}\n\npublic enum KimiCookieHeader {\n    private static let log = CodexBarLog.logger(LogCategories.kimiCookie)\n    private static let headerPatterns: [String] = [\n        #\"(?i)kimi-auth=([A-Za-z0-9._\\-+=/]+)\"#,\n        #\"(?i)-H\\s*'Cookie:\\s*([^']+)'\"#,\n        #\"(?i)-H\\s*\"Cookie:\\s*([^\"]+)\"\"#,\n        #\"(?i)\\bcookie:\\s*'([^']+)'\"#,\n        #\"(?i)\\bcookie:\\s*\"([^\"]+)\"\"#,\n        #\"(?i)\\bcookie:\\s*([^\\r\\n]+)\"#,\n    ]\n\n    public static func resolveCookieOverride(context: ProviderFetchContext) -> KimiCookieOverride? {\n        if let settings = context.settings?.kimi, settings.cookieSource == .manual {\n            if let manual = settings.manualCookieHeader, !manual.isEmpty {\n                return self.override(from: manual)\n            }\n        }\n\n        if let envToken = self.override(from: context.env[\"KIMI_MANUAL_COOKIE\"]) {\n            return envToken\n        }\n        if let envToken = self.override(from: context.env[\"KIMI_AUTH_TOKEN\"]) {\n            return envToken\n        }\n\n        return nil\n    }\n\n    public static func override(from raw: String?) -> KimiCookieOverride? {\n        guard let raw = raw?.trimmingCharacters(in: .whitespacesAndNewlines), !raw.isEmpty else {\n            return nil\n        }\n\n        if let token = self.extractKIMAuthToken(from: raw) {\n            return KimiCookieOverride(token: token)\n        }\n\n        if let cookieHeader = self.extractHeader(from: raw),\n           let token = self.extractKIMAuthToken(from: cookieHeader)\n        {\n            return KimiCookieOverride(token: token)\n        }\n\n        if raw.hasPrefix(\"eyJ\"), raw.split(separator: \".\").count == 3 {\n            return KimiCookieOverride(token: raw)\n        }\n\n        return nil\n    }\n\n    private static func extractKIMAuthToken(from raw: String) -> String? {\n        let patterns = [\n            #\"(?i)kimi-auth=([A-Za-z0-9._\\-+=/]+)\"#,\n            #\"(?i)kimi-auth:\\s*([A-Za-z0-9._\\-+=/]+)\"#,\n        ]\n\n        for pattern in patterns {\n            guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { continue }\n            let range = NSRange(raw.startIndex..<raw.endIndex, in: raw)\n            guard let match = regex.firstMatch(in: raw, options: [], range: range),\n                  match.numberOfRanges >= 2,\n                  let captureRange = Range(match.range(at: 1), in: raw)\n            else {\n                continue\n            }\n            let token = String(raw[captureRange]).trimmingCharacters(in: .whitespacesAndNewlines)\n            if !token.isEmpty { return token }\n        }\n\n        return nil\n    }\n\n    private static func extractHeader(from raw: String) -> String? {\n        for pattern in self.headerPatterns {\n            guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { continue }\n            let range = NSRange(raw.startIndex..<raw.endIndex, in: raw)\n            guard let match = regex.firstMatch(in: raw, options: [], range: range),\n                  match.numberOfRanges >= 2,\n                  let captureRange = Range(match.range(at: 1), in: raw)\n            else {\n                continue\n            }\n            let captured = String(raw[captureRange]).trimmingCharacters(in: .whitespacesAndNewlines)\n            if !captured.isEmpty { return captured }\n        }\n        return nil\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Kimi/KimiCookieImporter.swift",
    "content": "import Foundation\n\n#if os(macOS)\nimport SweetCookieKit\n\npublic enum KimiCookieImporter {\n    private static let log = CodexBarLog.logger(LogCategories.kimiCookie)\n    private static let cookieClient = BrowserCookieClient()\n    private static let cookieDomains = [\"www.kimi.com\", \"kimi.com\"]\n    private static let cookieImportOrder: BrowserCookieImportOrder =\n        ProviderDefaults.metadata[.kimi]?.browserCookieOrder ?? Browser.defaultImportOrder\n\n    public struct SessionInfo: Sendable {\n        public let cookies: [HTTPCookie]\n        public let sourceLabel: String\n\n        public init(cookies: [HTTPCookie], sourceLabel: String) {\n            self.cookies = cookies\n            self.sourceLabel = sourceLabel\n        }\n\n        public var authToken: String? {\n            self.cookies.first(where: { $0.name == \"kimi-auth\" })?.value\n        }\n    }\n\n    public static func importSessions(\n        browserDetection: BrowserDetection = BrowserDetection(),\n        logger: ((String) -> Void)? = nil) throws -> [SessionInfo]\n    {\n        var sessions: [SessionInfo] = []\n        let candidates = self.cookieImportOrder.cookieImportCandidates(using: browserDetection)\n        for browserSource in candidates {\n            do {\n                let perSource = try self.importSessions(from: browserSource, logger: logger)\n                sessions.append(contentsOf: perSource)\n            } catch {\n                BrowserCookieAccessGate.recordIfNeeded(error)\n                self.emit(\n                    \"\\(browserSource.displayName) cookie import failed: \\(error.localizedDescription)\",\n                    logger: logger)\n            }\n        }\n\n        guard !sessions.isEmpty else {\n            throw KimiCookieImportError.noCookies\n        }\n        return sessions\n    }\n\n    public static func importSessions(\n        from browserSource: Browser,\n        logger: ((String) -> Void)? = nil) throws -> [SessionInfo]\n    {\n        let query = BrowserCookieQuery(domains: self.cookieDomains)\n        let log: (String) -> Void = { msg in self.emit(msg, logger: logger) }\n        let sources = try Self.cookieClient.records(\n            matching: query,\n            in: browserSource,\n            logger: log)\n\n        var sessions: [SessionInfo] = []\n        let grouped = Dictionary(grouping: sources, by: { $0.store.profile.id })\n        let sortedGroups = grouped.values.sorted { lhs, rhs in\n            self.mergedLabel(for: lhs) < self.mergedLabel(for: rhs)\n        }\n\n        for group in sortedGroups where !group.isEmpty {\n            let label = self.mergedLabel(for: group)\n            let mergedRecords = self.mergeRecords(group)\n            guard !mergedRecords.isEmpty else { continue }\n            let httpCookies = BrowserCookieClient.makeHTTPCookies(mergedRecords, origin: query.origin)\n            guard !httpCookies.isEmpty else { continue }\n\n            // Only include sessions that have the kimi-auth cookie\n            guard httpCookies.contains(where: { $0.name == \"kimi-auth\" }) else {\n                continue\n            }\n\n            log(\"Found kimi-auth cookie in \\(label)\")\n            sessions.append(SessionInfo(cookies: httpCookies, sourceLabel: label))\n        }\n        return sessions\n    }\n\n    public static func importSession(\n        browserDetection: BrowserDetection = BrowserDetection(),\n        logger: ((String) -> Void)? = nil) throws -> SessionInfo\n    {\n        let sessions = try self.importSessions(browserDetection: browserDetection, logger: logger)\n        guard let first = sessions.first else {\n            throw KimiCookieImportError.noCookies\n        }\n        return first\n    }\n\n    public static func hasSession(\n        browserDetection: BrowserDetection = BrowserDetection(),\n        logger: ((String) -> Void)? = nil) -> Bool\n    {\n        do {\n            return try !self.importSessions(browserDetection: browserDetection, logger: logger).isEmpty\n        } catch {\n            return false\n        }\n    }\n\n    private static func cookieNames(from cookies: [HTTPCookie]) -> String {\n        let names = Set(cookies.map { \"\\($0.name)@\\($0.domain)\" }).sorted()\n        return names.joined(separator: \", \")\n    }\n\n    private static func emit(_ message: String, logger: ((String) -> Void)?) {\n        logger?(\"[kimi-cookie] \\(message)\")\n        self.log.debug(message)\n    }\n\n    private static func mergedLabel(for sources: [BrowserCookieStoreRecords]) -> String {\n        guard let base = sources.map(\\.label).min() else {\n            return \"Unknown\"\n        }\n        if base.hasSuffix(\" (Network)\") {\n            return String(base.dropLast(\" (Network)\".count))\n        }\n        return base\n    }\n\n    private static func mergeRecords(_ sources: [BrowserCookieStoreRecords]) -> [BrowserCookieRecord] {\n        let sortedSources = sources.sorted { lhs, rhs in\n            self.storePriority(lhs.store.kind) < self.storePriority(rhs.store.kind)\n        }\n        var mergedByKey: [String: BrowserCookieRecord] = [:]\n        for source in sortedSources {\n            for record in source.records {\n                let key = self.recordKey(record)\n                if let existing = mergedByKey[key] {\n                    if self.shouldReplace(existing: existing, candidate: record) {\n                        mergedByKey[key] = record\n                    }\n                } else {\n                    mergedByKey[key] = record\n                }\n            }\n        }\n        return Array(mergedByKey.values)\n    }\n\n    private static func storePriority(_ kind: BrowserCookieStoreKind) -> Int {\n        switch kind {\n        case .network: 0\n        case .primary: 1\n        case .safari: 2\n        }\n    }\n\n    private static func recordKey(_ record: BrowserCookieRecord) -> String {\n        \"\\(record.name)|\\(record.domain)|\\(record.path)\"\n    }\n\n    private static func shouldReplace(existing: BrowserCookieRecord, candidate: BrowserCookieRecord) -> Bool {\n        switch (existing.expires, candidate.expires) {\n        case let (lhs?, rhs?):\n            rhs > lhs\n        case (nil, .some):\n            true\n        case (.some, nil):\n            false\n        case (nil, nil):\n            false\n        }\n    }\n}\n\nenum KimiCookieImportError: LocalizedError {\n    case noCookies\n\n    var errorDescription: String? {\n        switch self {\n        case .noCookies:\n            \"No Kimi session cookies found in browsers.\"\n        }\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Kimi/KimiModels.swift",
    "content": "import Foundation\n\nstruct KimiUsageResponse: Codable {\n    let usages: [KimiUsage]\n}\n\nstruct KimiUsage: Codable {\n    let scope: String\n    let detail: KimiUsageDetail\n    let limits: [KimiRateLimit]?\n}\n\npublic struct KimiUsageDetail: Codable, Sendable {\n    public let limit: String\n    public let used: String?\n    public let remaining: String?\n    public let resetTime: String?\n\n    public init(limit: String, used: String?, remaining: String?, resetTime: String?) {\n        self.limit = limit\n        self.used = used\n        self.remaining = remaining\n        self.resetTime = resetTime\n    }\n}\n\nstruct KimiRateLimit: Codable {\n    let window: KimiWindow\n    let detail: KimiUsageDetail\n}\n\nstruct KimiWindow: Codable {\n    let duration: Int\n    let timeUnit: String\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Kimi/KimiProviderDescriptor.swift",
    "content": "import CodexBarMacroSupport\nimport Foundation\n\n@ProviderDescriptorRegistration\n@ProviderDescriptorDefinition\npublic enum KimiProviderDescriptor {\n    static func makeDescriptor() -> ProviderDescriptor {\n        ProviderDescriptor(\n            id: .kimi,\n            metadata: ProviderMetadata(\n                id: .kimi,\n                displayName: \"Kimi\",\n                sessionLabel: \"Weekly\",\n                weeklyLabel: \"Rate Limit\",\n                opusLabel: nil,\n                supportsOpus: false,\n                supportsCredits: false,\n                creditsHint: \"\",\n                toggleTitle: \"Show Kimi usage\",\n                cliName: \"kimi\",\n                defaultEnabled: false,\n                isPrimaryProvider: false,\n                usesAccountFallback: false,\n                browserCookieOrder: nil,\n                dashboardURL: \"https://www.kimi.com/code/console\",\n                statusPageURL: nil),\n            branding: ProviderBranding(\n                iconStyle: .kimi,\n                iconResourceName: \"ProviderIcon-kimi\",\n                color: ProviderColor(red: 254 / 255, green: 96 / 255, blue: 60 / 255)),\n            tokenCost: ProviderTokenCostConfig(\n                supportsTokenCost: false,\n                noDataMessage: { \"Kimi cost summary is not supported.\" }),\n            fetchPlan: ProviderFetchPlan(\n                sourceModes: [.auto, .web],\n                pipeline: ProviderFetchPipeline(resolveStrategies: { _ in [KimiWebFetchStrategy()] })),\n            cli: ProviderCLIConfig(\n                name: \"kimi\",\n                aliases: [\"kimi-ai\"],\n                versionDetector: nil))\n    }\n}\n\nstruct KimiWebFetchStrategy: ProviderFetchStrategy {\n    let id: String = \"kimi.web\"\n    let kind: ProviderFetchKind = .web\n    private static let log = CodexBarLog.logger(LogCategories.kimiWeb)\n\n    func isAvailable(_ context: ProviderFetchContext) async -> Bool {\n        if KimiCookieHeader.resolveCookieOverride(context: context) != nil {\n            return true\n        }\n\n        if Self.resolveToken(environment: context.env) != nil {\n            return true\n        }\n\n        #if os(macOS)\n        if context.settings?.kimi?.cookieSource != .off {\n            return KimiCookieImporter.hasSession()\n        }\n        #endif\n\n        return false\n    }\n\n    func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {\n        guard let token = self.resolveToken(context: context) else {\n            throw KimiAPIError.missingToken\n        }\n\n        let snapshot = try await KimiUsageFetcher.fetchUsage(authToken: token)\n        return self.makeResult(\n            usage: snapshot.toUsageSnapshot(),\n            sourceLabel: \"web\")\n    }\n\n    func shouldFallback(on error: Error, context: ProviderFetchContext) -> Bool {\n        if case KimiAPIError.missingToken = error { return false }\n        if case KimiAPIError.invalidToken = error { return false }\n        return true\n    }\n\n    private func resolveToken(context: ProviderFetchContext) -> String? {\n        // Check manual cookie first (highest priority when set)\n        if let override = KimiCookieHeader.resolveCookieOverride(context: context) {\n            return override.token\n        }\n\n        // Try browser cookie import when auto mode is enabled\n        #if os(macOS)\n        if context.settings?.kimi?.cookieSource != .off {\n            do {\n                let session = try KimiCookieImporter.importSession()\n                if let token = session.authToken {\n                    return token\n                }\n            } catch {\n                // No browser cookies found\n            }\n        }\n        #endif\n\n        // Fall back to environment\n        if let override = Self.resolveToken(environment: context.env) {\n            return override\n        }\n        return nil\n    }\n\n    private static func resolveToken(environment: [String: String]) -> String? {\n        ProviderTokenResolver.kimiAuthToken(environment: environment)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Kimi/KimiSettingsReader.swift",
    "content": "import Foundation\n\npublic enum KimiSettingsReader {\n    public static func authToken(environment: [String: String] = ProcessInfo.processInfo.environment) -> String? {\n        let raw = environment[\"KIMI_AUTH_TOKEN\"] ?? environment[\"kimi_auth_token\"]\n        return self.cleaned(raw)\n    }\n\n    private static func cleaned(_ raw: String?) -> String? {\n        guard var value = raw?.trimmingCharacters(in: .whitespacesAndNewlines), !value.isEmpty else {\n            return nil\n        }\n\n        if (value.hasPrefix(\"\\\"\") && value.hasSuffix(\"\\\"\")) ||\n            (value.hasPrefix(\"'\") && value.hasSuffix(\"'\"))\n        {\n            value.removeFirst()\n            value.removeLast()\n        }\n\n        value = value.trimmingCharacters(in: .whitespacesAndNewlines)\n        return value.isEmpty ? nil : value\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Kimi/KimiUsageFetcher.swift",
    "content": "import Foundation\n\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n\npublic struct KimiUsageFetcher: Sendable {\n    private static let log = CodexBarLog.logger(LogCategories.kimiAPI)\n    private static let usageURL =\n        URL(string: \"https://www.kimi.com/apiv2/kimi.gateway.billing.v1.BillingService/GetUsages\")!\n\n    public static func fetchUsage(authToken: String, now: Date = Date()) async throws -> KimiUsageSnapshot {\n        // Decode JWT to get session info\n        let sessionInfo = self.decodeSessionInfo(from: authToken)\n\n        var request = URLRequest(url: self.usageURL)\n        request.httpMethod = \"POST\"\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Content-Type\")\n        request.setValue(\"Bearer \\(authToken)\", forHTTPHeaderField: \"Authorization\")\n        request.setValue(\"kimi-auth=\\(authToken)\", forHTTPHeaderField: \"Cookie\")\n        request.setValue(\"https://www.kimi.com\", forHTTPHeaderField: \"Origin\")\n        request.setValue(\"https://www.kimi.com/code/console\", forHTTPHeaderField: \"Referer\")\n        request.setValue(\"*/*\", forHTTPHeaderField: \"Accept\")\n        request.setValue(\"en-US,en;q=0.9\", forHTTPHeaderField: \"Accept-Language\")\n        let userAgent = \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) \" +\n            \"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36\"\n        request.setValue(userAgent, forHTTPHeaderField: \"User-Agent\")\n        request.setValue(\"1\", forHTTPHeaderField: \"connect-protocol-version\")\n        request.setValue(\"en-US\", forHTTPHeaderField: \"x-language\")\n        request.setValue(\"web\", forHTTPHeaderField: \"x-msh-platform\")\n        request.setValue(TimeZone.current.identifier, forHTTPHeaderField: \"r-timezone\")\n\n        // Add session-specific headers from JWT\n        if let sessionInfo {\n            if let deviceId = sessionInfo.deviceId {\n                request.setValue(deviceId, forHTTPHeaderField: \"x-msh-device-id\")\n            }\n            if let sessionId = sessionInfo.sessionId {\n                request.setValue(sessionId, forHTTPHeaderField: \"x-msh-session-id\")\n            }\n            if let trafficId = sessionInfo.trafficId {\n                request.setValue(trafficId, forHTTPHeaderField: \"x-traffic-id\")\n            }\n        }\n\n        let requestBody = [\"scope\": [\"FEATURE_CODING\"]]\n        request.httpBody = try? JSONSerialization.data(withJSONObject: requestBody)\n\n        let (data, response) = try await URLSession.shared.data(for: request)\n        guard let httpResponse = response as? HTTPURLResponse else {\n            throw KimiAPIError.networkError(\"Invalid response\")\n        }\n\n        guard httpResponse.statusCode == 200 else {\n            let responseBody = String(data: data, encoding: .utf8) ?? \"<binary data>\"\n            Self.log.error(\"Kimi API returned \\(httpResponse.statusCode): \\(responseBody)\")\n\n            if httpResponse.statusCode == 401 {\n                throw KimiAPIError.invalidToken\n            }\n            if httpResponse.statusCode == 403 {\n                throw KimiAPIError.invalidToken\n            }\n            if httpResponse.statusCode == 400 {\n                throw KimiAPIError.invalidRequest(\"Bad request\")\n            }\n            throw KimiAPIError.apiError(\"HTTP \\(httpResponse.statusCode)\")\n        }\n\n        let usageResponse = try JSONDecoder().decode(KimiUsageResponse.self, from: data)\n        guard let codingUsage = usageResponse.usages.first(where: { $0.scope == \"FEATURE_CODING\" }) else {\n            throw KimiAPIError.parseFailed(\"FEATURE_CODING scope not found in response\")\n        }\n\n        return KimiUsageSnapshot(\n            weekly: codingUsage.detail,\n            rateLimit: codingUsage.limits?.first?.detail,\n            updatedAt: now)\n    }\n\n    private static func decodeSessionInfo(from jwt: String) -> SessionInfo? {\n        let parts = jwt.split(separator: \".\", maxSplits: 2)\n        guard parts.count == 3 else { return nil }\n\n        // Convert base64url to base64 for JWT decoding\n        // base64url uses - and _ instead of + and /\n        var payload = String(parts[1])\n            .replacingOccurrences(of: \"-\", with: \"+\")\n            .replacingOccurrences(of: \"_\", with: \"/\")\n        // Add padding if needed\n        while payload.count % 4 != 0 {\n            payload += \"=\"\n        }\n\n        guard let payloadData = Data(base64Encoded: payload),\n              let json = try? JSONSerialization.jsonObject(with: payloadData) as? [String: Any]\n        else {\n            return nil\n        }\n\n        return SessionInfo(\n            deviceId: json[\"device_id\"] as? String,\n            sessionId: json[\"ssid\"] as? String,\n            trafficId: json[\"sub\"] as? String)\n    }\n\n    private struct SessionInfo {\n        let deviceId: String?\n        let sessionId: String?\n        let trafficId: String?\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Kimi/KimiUsageSnapshot.swift",
    "content": "import Foundation\n\npublic struct KimiUsageSnapshot: Sendable {\n    public let weekly: KimiUsageDetail\n    public let rateLimit: KimiUsageDetail?\n    public let updatedAt: Date\n\n    public init(weekly: KimiUsageDetail, rateLimit: KimiUsageDetail?, updatedAt: Date) {\n        self.weekly = weekly\n        self.rateLimit = rateLimit\n        self.updatedAt = updatedAt\n    }\n\n    private static func parseDate(_ dateString: String?) -> Date? {\n        guard let dateString else { return nil }\n        let formatter = ISO8601DateFormatter()\n        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]\n        if let date = formatter.date(from: dateString) {\n            return date\n        }\n        let fallback = ISO8601DateFormatter()\n        fallback.formatOptions = [.withInternetDateTime]\n        return fallback.date(from: dateString)\n    }\n\n    private static func minutesFromNow(_ date: Date?) -> Int? {\n        guard let date else { return nil }\n        let minutes = Int(date.timeIntervalSince(Date()) / 60)\n        return minutes > 0 ? minutes : nil\n    }\n}\n\nextension KimiUsageSnapshot {\n    public func toUsageSnapshot() -> UsageSnapshot {\n        // Parse weekly quota\n        let weeklyLimit = Int(weekly.limit) ?? 0\n        let weeklyRemaining = Int(weekly.remaining ?? \"\")\n        let weeklyUsed = Int(weekly.used ?? \"\") ?? {\n            guard let remaining = weeklyRemaining else { return 0 }\n            return max(0, weeklyLimit - remaining)\n        }()\n\n        let weeklyPercent = weeklyLimit > 0 ? Double(weeklyUsed) / Double(weeklyLimit) * 100 : 0\n\n        let weeklyWindow = RateWindow(\n            usedPercent: weeklyPercent,\n            windowMinutes: nil, // Weekly doesn't have a fixed window like rate limit\n            resetsAt: Self.parseDate(self.weekly.resetTime),\n            resetDescription: \"\\(weeklyUsed)/\\(weeklyLimit) requests\")\n\n        // Parse rate limit if available\n        var rateLimitWindow: RateWindow?\n        if let rateLimit = self.rateLimit {\n            let rateLimitValue = Int(rateLimit.limit) ?? 0\n            let rateRemaining = Int(rateLimit.remaining ?? \"\")\n            let rateUsed = Int(rateLimit.used ?? \"\") ?? {\n                guard let remaining = rateRemaining else { return 0 }\n                return max(0, rateLimitValue - remaining)\n            }()\n            let ratePercent = rateLimitValue > 0 ? Double(rateUsed) / Double(rateLimitValue) * 100 : 0\n\n            rateLimitWindow = RateWindow(\n                usedPercent: ratePercent,\n                windowMinutes: 300, // 300 minutes = 5 hours\n                resetsAt: Self.parseDate(rateLimit.resetTime),\n                resetDescription: \"Rate: \\(rateUsed)/\\(rateLimitValue) per 5 hours\")\n        }\n\n        let identity = ProviderIdentitySnapshot(\n            providerID: .kimi,\n            accountEmail: nil,\n            accountOrganization: nil,\n            loginMethod: nil)\n\n        return UsageSnapshot(\n            primary: weeklyWindow,\n            secondary: rateLimitWindow,\n            tertiary: nil,\n            providerCost: nil,\n            updatedAt: self.updatedAt,\n            identity: identity)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/KimiK2/KimiK2ProviderDescriptor.swift",
    "content": "import CodexBarMacroSupport\nimport Foundation\n\n@ProviderDescriptorRegistration\n@ProviderDescriptorDefinition\npublic enum KimiK2ProviderDescriptor {\n    static func makeDescriptor() -> ProviderDescriptor {\n        ProviderDescriptor(\n            id: .kimik2,\n            metadata: ProviderMetadata(\n                id: .kimik2,\n                displayName: \"Kimi K2\",\n                sessionLabel: \"Credits\",\n                weeklyLabel: \"Credits\",\n                opusLabel: nil,\n                supportsOpus: false,\n                supportsCredits: false,\n                creditsHint: \"\",\n                toggleTitle: \"Show Kimi K2 usage\",\n                cliName: \"kimik2\",\n                defaultEnabled: false,\n                isPrimaryProvider: false,\n                usesAccountFallback: false,\n                browserCookieOrder: nil,\n                dashboardURL: \"https://kimi-k2.ai/my-credits\",\n                statusPageURL: nil),\n            branding: ProviderBranding(\n                iconStyle: .kimi,\n                iconResourceName: \"ProviderIcon-kimi\",\n                color: ProviderColor(red: 76 / 255, green: 0 / 255, blue: 255 / 255)),\n            tokenCost: ProviderTokenCostConfig(\n                supportsTokenCost: false,\n                noDataMessage: { \"Kimi K2 cost summary is not available.\" }),\n            fetchPlan: ProviderFetchPlan(\n                sourceModes: [.auto, .api],\n                pipeline: ProviderFetchPipeline(resolveStrategies: { _ in [KimiK2APIFetchStrategy()] })),\n            cli: ProviderCLIConfig(\n                name: \"kimik2\",\n                aliases: [\"kimi-k2\", \"kimiK2\"],\n                versionDetector: nil))\n    }\n}\n\nstruct KimiK2APIFetchStrategy: ProviderFetchStrategy {\n    let id: String = \"kimik2.api\"\n    let kind: ProviderFetchKind = .apiToken\n\n    func isAvailable(_ context: ProviderFetchContext) async -> Bool {\n        Self.resolveToken(environment: context.env) != nil\n    }\n\n    func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {\n        guard let apiKey = Self.resolveToken(environment: context.env) else {\n            throw KimiK2UsageError.missingCredentials\n        }\n        let usage = try await KimiK2UsageFetcher.fetchUsage(apiKey: apiKey)\n        return self.makeResult(\n            usage: usage.toUsageSnapshot(),\n            sourceLabel: \"api\")\n    }\n\n    func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool {\n        false\n    }\n\n    private static func resolveToken(environment: [String: String]) -> String? {\n        ProviderTokenResolver.kimiK2Token(environment: environment)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/KimiK2/KimiK2SettingsReader.swift",
    "content": "import Foundation\n\npublic struct KimiK2SettingsReader: Sendable {\n    public static let apiKeyEnvironmentKeys = [\n        \"KIMI_K2_API_KEY\",\n        \"KIMI_API_KEY\",\n        \"KIMI_KEY\",\n    ]\n\n    public static func apiKey(\n        environment: [String: String] = ProcessInfo.processInfo.environment) -> String?\n    {\n        for key in self.apiKeyEnvironmentKeys {\n            guard let raw = environment[key]?.trimmingCharacters(in: .whitespacesAndNewlines),\n                  !raw.isEmpty\n            else {\n                continue\n            }\n            let cleaned = Self.cleaned(raw)\n            if !cleaned.isEmpty {\n                return cleaned\n            }\n        }\n        return nil\n    }\n\n    private static func cleaned(_ raw: String) -> String {\n        var value = raw\n        if (value.hasPrefix(\"\\\"\") && value.hasSuffix(\"\\\"\")) ||\n            (value.hasPrefix(\"'\") && value.hasSuffix(\"'\"))\n        {\n            value.removeFirst()\n            value.removeLast()\n        }\n        return value.trimmingCharacters(in: .whitespacesAndNewlines)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/KimiK2/KimiK2UsageFetcher.swift",
    "content": "import Foundation\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n\npublic struct KimiK2UsageSnapshot: Sendable {\n    public let summary: KimiK2UsageSummary\n\n    public init(summary: KimiK2UsageSummary) {\n        self.summary = summary\n    }\n\n    public func toUsageSnapshot() -> UsageSnapshot {\n        self.summary.toUsageSnapshot()\n    }\n}\n\npublic struct KimiK2UsageSummary: Sendable {\n    public let consumed: Double\n    public let remaining: Double\n    public let averageTokens: Double?\n    public let updatedAt: Date\n\n    public init(consumed: Double, remaining: Double, averageTokens: Double?, updatedAt: Date) {\n        self.consumed = consumed\n        self.remaining = remaining\n        self.averageTokens = averageTokens\n        self.updatedAt = updatedAt\n    }\n\n    public func toUsageSnapshot() -> UsageSnapshot {\n        let total = max(0, self.consumed + self.remaining)\n        let usedPercent: Double = if total > 0 {\n            min(100, max(0, (self.consumed / total) * 100))\n        } else {\n            0\n        }\n        let usedText = String(format: \"%.0f\", self.consumed)\n        let totalText = String(format: \"%.0f\", total)\n        let rateWindow = RateWindow(\n            usedPercent: usedPercent,\n            windowMinutes: nil,\n            resetsAt: nil,\n            resetDescription: total > 0 ? \"Credits: \\(usedText)/\\(totalText)\" : nil)\n        let identity = ProviderIdentitySnapshot(\n            providerID: .kimik2,\n            accountEmail: nil,\n            accountOrganization: nil,\n            loginMethod: nil)\n        return UsageSnapshot(\n            primary: rateWindow,\n            secondary: nil,\n            tertiary: nil,\n            providerCost: nil,\n            updatedAt: self.updatedAt,\n            identity: identity)\n    }\n}\n\npublic enum KimiK2UsageError: LocalizedError, Sendable {\n    case missingCredentials\n    case networkError(String)\n    case apiError(String)\n    case parseFailed(String)\n\n    public var errorDescription: String? {\n        switch self {\n        case .missingCredentials:\n            \"Missing Kimi K2 API key.\"\n        case let .networkError(message):\n            \"Kimi K2 network error: \\(message)\"\n        case let .apiError(message):\n            \"Kimi K2 API error: \\(message)\"\n        case let .parseFailed(message):\n            \"Failed to parse Kimi K2 response: \\(message)\"\n        }\n    }\n}\n\npublic struct KimiK2UsageFetcher: Sendable {\n    private static let log = CodexBarLog.logger(LogCategories.kimiK2Usage)\n    private static let creditsURL = URL(string: \"https://kimi-k2.ai/api/user/credits\")!\n    private static let jsonSerializer = JSONSerialization.self\n    private static let consumedPaths: [[String]] = [\n        [\"total_credits_consumed\"],\n        [\"totalCreditsConsumed\"],\n        [\"total_credits_used\"],\n        [\"totalCreditsUsed\"],\n        [\"credits_consumed\"],\n        [\"creditsConsumed\"],\n        [\"consumedCredits\"],\n        [\"usedCredits\"],\n        [\"total\"],\n        [\"usage\", \"total\"],\n        [\"usage\", \"consumed\"],\n    ]\n\n    private static let remainingPaths: [[String]] = [\n        [\"credits_remaining\"],\n        [\"creditsRemaining\"],\n        [\"remaining_credits\"],\n        [\"remainingCredits\"],\n        [\"available_credits\"],\n        [\"availableCredits\"],\n        [\"credits_left\"],\n        [\"creditsLeft\"],\n        [\"usage\", \"credits_remaining\"],\n        [\"usage\", \"remaining\"],\n    ]\n\n    private static let averageTokenPaths: [[String]] = [\n        [\"average_tokens_per_request\"],\n        [\"averageTokensPerRequest\"],\n        [\"average_tokens\"],\n        [\"averageTokens\"],\n        [\"avg_tokens\"],\n        [\"avgTokens\"],\n    ]\n\n    private static let timestampPaths: [[String]] = [\n        [\"updated_at\"],\n        [\"updatedAt\"],\n        [\"timestamp\"],\n        [\"time\"],\n        [\"last_update\"],\n        [\"lastUpdated\"],\n    ]\n\n    public static func fetchUsage(apiKey: String) async throws -> KimiK2UsageSnapshot {\n        guard !apiKey.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {\n            throw KimiK2UsageError.missingCredentials\n        }\n\n        var request = URLRequest(url: self.creditsURL)\n        request.httpMethod = \"GET\"\n        request.setValue(\"Bearer \\(apiKey)\", forHTTPHeaderField: \"Authorization\")\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Accept\")\n\n        let (data, response) = try await URLSession.shared.data(for: request)\n\n        guard let httpResponse = response as? HTTPURLResponse else {\n            throw KimiK2UsageError.networkError(\"Invalid response\")\n        }\n\n        guard httpResponse.statusCode == 200 else {\n            let body = String(data: data, encoding: .utf8) ?? \"HTTP \\(httpResponse.statusCode)\"\n            Self.log.error(\"Kimi K2 API returned \\(httpResponse.statusCode): \\(body)\")\n            throw KimiK2UsageError.apiError(body)\n        }\n\n        if let jsonString = String(data: data, encoding: .utf8) {\n            Self.log.debug(\"Kimi K2 API response: \\(jsonString)\")\n        }\n\n        let summary = try Self.parseSummary(data: data, headers: httpResponse.allHeaderFields)\n        return KimiK2UsageSnapshot(summary: summary)\n    }\n\n    static func _parseSummaryForTesting(_ data: Data, headers: [AnyHashable: Any] = [:]) throws -> KimiK2UsageSummary {\n        try self.parseSummary(data: data, headers: headers)\n    }\n\n    private static func parseSummary(data: Data, headers: [AnyHashable: Any]) throws -> KimiK2UsageSummary {\n        guard let json = try? jsonSerializer.jsonObject(with: data),\n              let dictionary = json as? [String: Any]\n        else {\n            throw KimiK2UsageError.parseFailed(\"Root JSON is not an object.\")\n        }\n\n        let contexts = Self.contexts(from: dictionary)\n        let consumed = Self.doubleValue(for: Self.consumedPaths, in: contexts) ?? 0\n        let remaining = Self.doubleValue(for: Self.remainingPaths, in: contexts)\n            ?? Self.doubleValueFromHeaders(headers: headers, key: \"x-credits-remaining\")\n            ?? 0\n        let averageTokens = Self.doubleValue(for: Self.averageTokenPaths, in: contexts)\n        let updatedAt = Self.dateValue(for: Self.timestampPaths, in: contexts) ?? Date()\n\n        return KimiK2UsageSummary(\n            consumed: consumed,\n            remaining: max(0, remaining),\n            averageTokens: averageTokens,\n            updatedAt: updatedAt)\n    }\n\n    private static func contexts(from dictionary: [String: Any]) -> [[String: Any]] {\n        var contexts: [[String: Any]] = [dictionary]\n        if let data = dictionary[\"data\"] as? [String: Any] {\n            contexts.append(data)\n            if let dataUsage = data[\"usage\"] as? [String: Any] {\n                contexts.append(dataUsage)\n            }\n            if let dataCredits = data[\"credits\"] as? [String: Any] {\n                contexts.append(dataCredits)\n            }\n        }\n        if let result = dictionary[\"result\"] as? [String: Any] {\n            contexts.append(result)\n            if let resultUsage = result[\"usage\"] as? [String: Any] {\n                contexts.append(resultUsage)\n            }\n            if let resultCredits = result[\"credits\"] as? [String: Any] {\n                contexts.append(resultCredits)\n            }\n        }\n        if let usage = dictionary[\"usage\"] as? [String: Any] {\n            contexts.append(usage)\n        }\n        if let credits = dictionary[\"credits\"] as? [String: Any] {\n            contexts.append(credits)\n        }\n        return contexts\n    }\n\n    private static func doubleValue(\n        for paths: [[String]],\n        in contexts: [[String: Any]]) -> Double?\n    {\n        for path in paths {\n            if let raw = self.value(for: path, in: contexts),\n               let value = self.double(from: raw)\n            {\n                return value\n            }\n        }\n        return nil\n    }\n\n    private static func dateValue(\n        for paths: [[String]],\n        in contexts: [[String: Any]]) -> Date?\n    {\n        for path in paths {\n            if let raw = self.value(for: path, in: contexts) {\n                if let date = self.date(from: raw) {\n                    return date\n                }\n            }\n        }\n        return nil\n    }\n\n    private static func value(for path: [String], in contexts: [[String: Any]]) -> Any? {\n        for context in contexts {\n            var cursor: Any? = context\n            for key in path {\n                if let dict = cursor as? [String: Any] {\n                    cursor = dict[key]\n                } else {\n                    cursor = nil\n                }\n            }\n            if cursor != nil {\n                return cursor\n            }\n        }\n        return nil\n    }\n\n    private static func double(from raw: Any) -> Double? {\n        if let value = raw as? Double {\n            return value\n        }\n        if let value = raw as? Int {\n            return Double(value)\n        }\n        if let value = raw as? String {\n            return Double(value)\n        }\n        return nil\n    }\n\n    private static func date(from raw: Any) -> Date? {\n        if let value = raw as? Date {\n            return value\n        }\n        if let value = raw as? Double {\n            return self.dateFromNumeric(value)\n        }\n        if let value = raw as? Int {\n            return self.dateFromNumeric(Double(value))\n        }\n        if let value = raw as? String {\n            let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)\n            if let numeric = Double(trimmed) {\n                return self.dateFromNumeric(numeric)\n            }\n            let formatter = ISO8601DateFormatter()\n            formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]\n            if let date = formatter.date(from: value) {\n                return date\n            }\n            let fallback = ISO8601DateFormatter()\n            fallback.formatOptions = [.withInternetDateTime]\n            return fallback.date(from: value)\n        }\n        return nil\n    }\n\n    private static func dateFromNumeric(_ value: Double) -> Date? {\n        guard value > 0 else { return nil }\n        if value > 1_000_000_000_000 {\n            return Date(timeIntervalSince1970: value / 1000)\n        }\n        return Date(timeIntervalSince1970: value)\n    }\n\n    private static func doubleValueFromHeaders(headers: [AnyHashable: Any], key: String) -> Double? {\n        for (headerKey, value) in headers {\n            guard let headerKey = headerKey as? String else { continue }\n            if headerKey.lowercased() == key.lowercased() {\n                return self.double(from: value)\n            }\n        }\n        return nil\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Kiro/KiroProviderDescriptor.swift",
    "content": "import CodexBarMacroSupport\nimport Foundation\n\n@ProviderDescriptorRegistration\n@ProviderDescriptorDefinition\npublic enum KiroProviderDescriptor {\n    static func makeDescriptor() -> ProviderDescriptor {\n        ProviderDescriptor(\n            id: .kiro,\n            metadata: ProviderMetadata(\n                id: .kiro,\n                displayName: \"Kiro\",\n                sessionLabel: \"Credits\",\n                weeklyLabel: \"Bonus\",\n                opusLabel: nil,\n                supportsOpus: false,\n                supportsCredits: false,\n                creditsHint: \"\",\n                toggleTitle: \"Show Kiro usage\",\n                cliName: \"kiro\",\n                defaultEnabled: false,\n                isPrimaryProvider: false,\n                usesAccountFallback: false,\n                dashboardURL: \"https://app.kiro.dev/account/usage\",\n                statusPageURL: nil,\n                statusLinkURL: \"https://health.aws.amazon.com/health/status\"),\n            branding: ProviderBranding(\n                iconStyle: .kiro,\n                iconResourceName: \"ProviderIcon-kiro\",\n                color: ProviderColor(red: 255 / 255, green: 153 / 255, blue: 0 / 255)),\n            tokenCost: ProviderTokenCostConfig(\n                supportsTokenCost: false,\n                noDataMessage: { \"Kiro cost summary is not supported.\" }),\n            fetchPlan: ProviderFetchPlan(\n                sourceModes: [.auto, .cli],\n                pipeline: ProviderFetchPipeline(resolveStrategies: { _ in [KiroCLIFetchStrategy()] })),\n            cli: ProviderCLIConfig(\n                name: \"kiro\",\n                aliases: [\"kiro-cli\"],\n                versionDetector: { _ in KiroStatusProbe.detectVersion() }))\n    }\n}\n\nstruct KiroCLIFetchStrategy: ProviderFetchStrategy {\n    let id: String = \"kiro.cli\"\n    let kind: ProviderFetchKind = .cli\n\n    func isAvailable(_: ProviderFetchContext) async -> Bool {\n        TTYCommandRunner.which(\"kiro-cli\") != nil\n    }\n\n    func fetch(_: ProviderFetchContext) async throws -> ProviderFetchResult {\n        let probe = KiroStatusProbe()\n        let snap = try await probe.fetch()\n        return self.makeResult(\n            usage: snap.toUsageSnapshot(),\n            sourceLabel: \"cli\")\n    }\n\n    func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool {\n        false\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Kiro/KiroStatusProbe.swift",
    "content": "import Foundation\n\npublic struct KiroUsageSnapshot: Sendable {\n    public let planName: String\n    public let creditsUsed: Double\n    public let creditsTotal: Double\n    public let creditsPercent: Double\n    public let bonusCreditsUsed: Double?\n    public let bonusCreditsTotal: Double?\n    public let bonusExpiryDays: Int?\n    public let resetsAt: Date?\n    public let updatedAt: Date\n\n    public func toUsageSnapshot() -> UsageSnapshot {\n        let primary = RateWindow(\n            usedPercent: self.creditsPercent,\n            windowMinutes: nil,\n            resetsAt: self.resetsAt,\n            resetDescription: nil)\n\n        var secondary: RateWindow?\n        if let bonusUsed = self.bonusCreditsUsed,\n           let bonusTotal = self.bonusCreditsTotal,\n           bonusTotal > 0\n        {\n            let bonusPercent = (bonusUsed / bonusTotal) * 100.0\n            var expiryDate: Date?\n            if let days = self.bonusExpiryDays {\n                expiryDate = Calendar.current.date(byAdding: .day, value: days, to: Date())\n            }\n            secondary = RateWindow(\n                usedPercent: bonusPercent,\n                windowMinutes: nil,\n                resetsAt: expiryDate,\n                resetDescription: self.bonusExpiryDays.map { \"expires in \\($0)d\" })\n        }\n\n        let identity = ProviderIdentitySnapshot(\n            providerID: .kiro,\n            accountEmail: nil,\n            accountOrganization: self.planName,\n            loginMethod: self.planName)\n\n        return UsageSnapshot(\n            primary: primary,\n            secondary: secondary,\n            tertiary: nil,\n            providerCost: nil,\n            zaiUsage: nil,\n            updatedAt: self.updatedAt,\n            identity: identity)\n    }\n}\n\npublic enum KiroStatusProbeError: LocalizedError, Sendable {\n    case cliNotFound\n    case notLoggedIn\n    case cliFailed(String)\n    case parseError(String)\n    case timeout\n\n    public var errorDescription: String? {\n        switch self {\n        case .cliNotFound:\n            \"kiro-cli not found. Install it from https://kiro.dev\"\n        case .notLoggedIn:\n            \"Not logged in to Kiro. Run 'kiro-cli login' first.\"\n        case let .cliFailed(message):\n            message\n        case let .parseError(msg):\n            \"Failed to parse Kiro usage: \\(msg)\"\n        case .timeout:\n            \"Kiro CLI timed out.\"\n        }\n    }\n}\n\npublic struct KiroStatusProbe: Sendable {\n    public init() {}\n\n    private static let logger = CodexBarLog.logger(LogCategories.kiro)\n\n    public static func detectVersion() -> String? {\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: \"/usr/bin/env\")\n        process.arguments = [\"kiro-cli\", \"--version\"]\n        let pipe = Pipe()\n        process.standardOutput = pipe\n        process.standardError = pipe\n        do {\n            try process.run()\n            process.waitUntilExit()\n            let data = pipe.fileHandleForReading.readDataToEndOfFile()\n            guard let output = String(data: data, encoding: .utf8) else { return nil }\n            // Output is like \"kiro-cli 1.23.1\"\n            let trimmed = output.trimmingCharacters(in: .whitespacesAndNewlines)\n            if trimmed.hasPrefix(\"kiro-cli \") {\n                return String(trimmed.dropFirst(\"kiro-cli \".count))\n            }\n            return trimmed.isEmpty ? nil : trimmed\n        } catch {\n            self.logger.debug(\"kiro-cli version detection failed: \\(error.localizedDescription)\")\n            return nil\n        }\n    }\n\n    public func fetch() async throws -> KiroUsageSnapshot {\n        try await self.ensureLoggedIn()\n        let output = try await self.runUsageCommand()\n        return try self.parse(output: output)\n    }\n\n    private struct KiroCLIResult {\n        let stdout: String\n        let stderr: String\n        let terminationStatus: Int32\n        let terminatedForIdle: Bool\n    }\n\n    private func ensureLoggedIn() async throws {\n        let result = try await self.runCommand(arguments: [\"whoami\"], timeout: 5.0)\n        try self.validateWhoAmIOutput(\n            stdout: result.stdout,\n            stderr: result.stderr,\n            terminationStatus: result.terminationStatus)\n    }\n\n    func validateWhoAmIOutput(stdout: String, stderr: String, terminationStatus: Int32) throws {\n        let trimmedStdout = stdout.trimmingCharacters(in: .whitespacesAndNewlines)\n        let trimmedStderr = stderr.trimmingCharacters(in: .whitespacesAndNewlines)\n        let combined = trimmedStderr.isEmpty ? trimmedStdout : trimmedStderr\n        let lowered = combined.lowercased()\n\n        if lowered.contains(\"not logged in\") || lowered.contains(\"login required\") {\n            throw KiroStatusProbeError.notLoggedIn\n        }\n\n        if terminationStatus != 0 {\n            let message = combined.isEmpty\n                ? \"Kiro CLI failed with status \\(terminationStatus).\"\n                : combined\n            throw KiroStatusProbeError.cliFailed(message)\n        }\n\n        if combined.isEmpty {\n            throw KiroStatusProbeError.cliFailed(\"Kiro CLI whoami returned no output.\")\n        }\n    }\n\n    private func runUsageCommand() async throws -> String {\n        let result = try await self.runCommand(\n            arguments: [\"chat\", \"--no-interactive\", \"/usage\"],\n            timeout: 20.0,\n            idleTimeout: 10.0)\n        let trimmedStdout = result.stdout.trimmingCharacters(in: .whitespacesAndNewlines)\n        let trimmedStderr = result.stderr.trimmingCharacters(in: .whitespacesAndNewlines)\n        let combinedOutput = trimmedStderr.isEmpty ? trimmedStdout : trimmedStderr\n        let combinedStripped = Self.stripANSI(combinedOutput).lowercased()\n\n        if combinedStripped.contains(\"not logged in\")\n            || combinedStripped.contains(\"login required\")\n            || combinedStripped.contains(\"failed to initialize auth portal\")\n            || combinedStripped.contains(\"kiro-cli login\")\n            || combinedStripped.contains(\"oauth error\")\n        {\n            throw KiroStatusProbeError.notLoggedIn\n        }\n\n        if result.terminatedForIdle, !Self.isUsageOutputComplete(combinedOutput) {\n            throw KiroStatusProbeError.timeout\n        }\n\n        if !trimmedStdout.isEmpty {\n            return result.stdout\n        }\n\n        if !trimmedStderr.isEmpty {\n            return result.stderr\n        }\n\n        if result.terminationStatus != 0 {\n            let message = combinedOutput.isEmpty\n                ? \"Kiro CLI failed with status \\(result.terminationStatus).\"\n                : combinedOutput\n            throw KiroStatusProbeError.cliFailed(message)\n        }\n\n        return result.stdout\n    }\n\n    private func runCommand(\n        arguments: [String],\n        timeout: TimeInterval,\n        idleTimeout: TimeInterval = 5.0) async throws -> KiroCLIResult\n    {\n        guard let binary = TTYCommandRunner.which(\"kiro-cli\") else {\n            throw KiroStatusProbeError.cliNotFound\n        }\n\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: binary)\n        process.arguments = arguments\n\n        let stdoutPipe = Pipe()\n        let stderrPipe = Pipe()\n        process.standardOutput = stdoutPipe\n        process.standardError = stderrPipe\n        process.standardInput = FileHandle.nullDevice\n\n        var env = ProcessInfo.processInfo.environment\n        env[\"TERM\"] = \"xterm-256color\"\n        process.environment = env\n\n        // Thread-safe state for activity tracking\n        final class ActivityState: @unchecked Sendable {\n            private let lock = NSLock()\n            private var _lastActivityAt = Date()\n            private var _hasReceivedOutput = false\n            private var _stdoutData = Data()\n            private var _stderrData = Data()\n\n            var lastActivityAt: Date {\n                self.lock.lock()\n                defer { lock.unlock() }\n                return self._lastActivityAt\n            }\n\n            var hasReceivedOutput: Bool {\n                self.lock.lock()\n                defer { lock.unlock() }\n                return self._hasReceivedOutput\n            }\n\n            func appendStdout(_ data: Data) {\n                self.lock.lock()\n                defer { lock.unlock() }\n                self._stdoutData.append(data)\n                self._lastActivityAt = Date()\n                self._hasReceivedOutput = true\n            }\n\n            func appendStderr(_ data: Data) {\n                self.lock.lock()\n                defer { lock.unlock() }\n                self._stderrData.append(data)\n                self._lastActivityAt = Date()\n                self._hasReceivedOutput = true\n            }\n\n            func getOutput() -> (stdout: Data, stderr: Data) {\n                self.lock.lock()\n                defer { lock.unlock() }\n                return (self._stdoutData, self._stderrData)\n            }\n        }\n\n        let state = ActivityState()\n\n        // Set up readability handlers to track activity\n        stdoutPipe.fileHandleForReading.readabilityHandler = { handle in\n            let data = handle.availableData\n            if !data.isEmpty {\n                state.appendStdout(data)\n            }\n        }\n        stderrPipe.fileHandleForReading.readabilityHandler = { handle in\n            let data = handle.availableData\n            if !data.isEmpty {\n                state.appendStderr(data)\n            }\n        }\n\n        return try await withCheckedThrowingContinuation { continuation in\n            DispatchQueue.global().async {\n                do {\n                    try process.run()\n                } catch {\n                    stdoutPipe.fileHandleForReading.readabilityHandler = nil\n                    stderrPipe.fileHandleForReading.readabilityHandler = nil\n                    continuation.resume(throwing: error)\n                    return\n                }\n\n                let deadline = Date().addingTimeInterval(timeout)\n                var didHitDeadline = false\n                var didTerminateForIdle = false\n\n                while process.isRunning {\n                    if Date() >= deadline {\n                        didHitDeadline = true\n                        break\n                    }\n                    // Idle timeout: if we got output but then it went silent\n                    if state.hasReceivedOutput,\n                       Date().timeIntervalSince(state.lastActivityAt) >= idleTimeout\n                    {\n                        // Process went idle after producing output - likely done or stuck\n                        didTerminateForIdle = true\n                        break\n                    }\n                    Thread.sleep(forTimeInterval: 0.1)\n                }\n\n                // Clean up handlers\n                stdoutPipe.fileHandleForReading.readabilityHandler = nil\n                stderrPipe.fileHandleForReading.readabilityHandler = nil\n\n                if process.isRunning {\n                    process.terminate()\n                    process.waitUntilExit()\n                    if didHitDeadline || !state.hasReceivedOutput {\n                        continuation.resume(throwing: KiroStatusProbeError.timeout)\n                        return\n                    }\n                }\n\n                // Read any remaining data\n                let remainingStdout = stdoutPipe.fileHandleForReading.readDataToEndOfFile()\n                let remainingStderr = stderrPipe.fileHandleForReading.readDataToEndOfFile()\n\n                var output = state.getOutput()\n                output.stdout.append(remainingStdout)\n                output.stderr.append(remainingStderr)\n\n                let stdoutOutput = String(data: output.stdout, encoding: .utf8) ?? \"\"\n                let stderrOutput = String(data: output.stderr, encoding: .utf8) ?? \"\"\n                continuation.resume(returning: KiroCLIResult(\n                    stdout: stdoutOutput,\n                    stderr: stderrOutput,\n                    terminationStatus: process.terminationStatus,\n                    terminatedForIdle: didTerminateForIdle))\n            }\n        }\n    }\n\n    func parse(output: String) throws -> KiroUsageSnapshot {\n        let stripped = Self.stripANSI(output)\n\n        let trimmed = stripped.trimmingCharacters(in: .whitespacesAndNewlines)\n        if trimmed.isEmpty {\n            throw KiroStatusProbeError.parseError(\"Empty output from kiro-cli.\")\n        }\n\n        let lowered = stripped.lowercased()\n        if lowered.contains(\"could not retrieve usage information\") {\n            throw KiroStatusProbeError.parseError(\"Kiro CLI could not retrieve usage information.\")\n        }\n\n        // Check for not logged in\n        if lowered.contains(\"not logged in\")\n            || lowered.contains(\"login required\")\n            || lowered.contains(\"failed to initialize auth portal\")\n            || lowered.contains(\"kiro-cli login\")\n            || lowered.contains(\"oauth error\")\n        {\n            throw KiroStatusProbeError.notLoggedIn\n        }\n\n        // Track which key patterns matched to detect format changes\n        var matchedPercent = false\n        var matchedCredits = false\n        var matchedNewFormat = false\n\n        // Parse plan name from \"| KIRO FREE\" or similar (legacy format)\n        var planName = \"Kiro\"\n        if let planMatch = stripped.range(of: #\"\\|\\s*(KIRO\\s+\\w+)\"#, options: .regularExpression) {\n            let raw = String(stripped[planMatch]).replacingOccurrences(of: \"|\", with: \"\")\n            planName = raw.trimmingCharacters(in: .whitespaces)\n        }\n\n        // Parse plan name from \"Plan: Q Developer Pro\" (new format, kiro-cli 1.24+)\n        if let newPlanMatch = stripped.range(of: #\"Plan:\\s*(.+)\"#, options: .regularExpression) {\n            let line = String(stripped[newPlanMatch])\n            // Extract just the plan name, stopping at newline\n            let planLine = line.replacingOccurrences(of: \"Plan:\", with: \"\").trimmingCharacters(in: .whitespaces)\n            if let firstLine = planLine.split(separator: \"\\n\").first {\n                planName = String(firstLine).trimmingCharacters(in: .whitespaces)\n                matchedNewFormat = true\n            }\n        }\n\n        // Check if this is a managed plan with no usage data\n        let isManagedPlan = lowered.contains(\"managed by admin\")\n            || lowered.contains(\"managed by organization\")\n\n        // Parse reset date from \"resets on 01/01\"\n        var resetsAt: Date?\n        if let resetMatch = stripped.range(of: #\"resets on (\\d{2}/\\d{2})\"#, options: .regularExpression) {\n            let resetStr = String(stripped[resetMatch])\n            if let dateRange = resetStr.range(of: #\"\\d{2}/\\d{2}\"#, options: .regularExpression) {\n                let dateStr = String(resetStr[dateRange])\n                resetsAt = Self.parseResetDate(dateStr)\n            }\n        }\n\n        // Parse credits percentage from \"████...█ X%\"\n        var creditsPercent: Double = 0\n        if let percentMatch = stripped.range(of: #\"█+\\s*(\\d+)%\"#, options: .regularExpression) {\n            let percentStr = String(stripped[percentMatch])\n            if let numMatch = percentStr.range(of: #\"\\d+\"#, options: .regularExpression) {\n                creditsPercent = Double(String(percentStr[numMatch])) ?? 0\n                matchedPercent = true\n            }\n        }\n\n        // Parse credits used/total from \"(X.XX of Y covered in plan)\"\n        var creditsUsed: Double = 0\n        var creditsTotal: Double = 50 // default free tier\n        let creditsPattern = #\"\\((\\d+\\.?\\d*)\\s+of\\s+(\\d+)\\s+covered\"#\n        if let creditsMatch = stripped.range(of: creditsPattern, options: .regularExpression) {\n            let creditsStr = String(stripped[creditsMatch])\n            let numbers = creditsStr.matches(of: /(\\d+\\.?\\d*)/)\n            if numbers.count >= 2 {\n                creditsUsed = Double(String(numbers[0].output.1)) ?? 0\n                creditsTotal = Double(String(numbers[1].output.1)) ?? 50\n                matchedCredits = true\n            }\n        }\n        if !matchedPercent, matchedCredits, creditsTotal > 0 {\n            creditsPercent = (creditsUsed / creditsTotal) * 100.0\n        }\n\n        // Parse bonus credits from \"Bonus credits: X.XX/Y credits used, expires in Z days\"\n        var bonusUsed: Double?\n        var bonusTotal: Double?\n        var bonusExpiryDays: Int?\n        if let bonusMatch = stripped.range(of: #\"Bonus credits:\\s*(\\d+\\.?\\d*)/(\\d+)\"#, options: .regularExpression) {\n            let bonusStr = String(stripped[bonusMatch])\n            let numbers = bonusStr.matches(of: /(\\d+\\.?\\d*)/)\n            if numbers.count >= 2 {\n                bonusUsed = Double(String(numbers[0].output.1))\n                bonusTotal = Double(String(numbers[1].output.1))\n            }\n        }\n        if let expiryMatch = stripped.range(of: #\"expires in (\\d+) days?\"#, options: .regularExpression) {\n            let expiryStr = String(stripped[expiryMatch])\n            if let numMatch = expiryStr.range(of: #\"\\d+\"#, options: .regularExpression) {\n                bonusExpiryDays = Int(String(expiryStr[numMatch]))\n            }\n        }\n\n        // Managed plans in new format may omit usage metrics. Only fall back to zeros when\n        // we did not parse any usage values, so we do not mask real metrics.\n        if matchedNewFormat, isManagedPlan, !matchedPercent, !matchedCredits {\n            // Managed plans don't expose credits; return snapshot with plan name only\n            return KiroUsageSnapshot(\n                planName: planName,\n                creditsUsed: 0,\n                creditsTotal: 0,\n                creditsPercent: 0,\n                bonusCreditsUsed: nil,\n                bonusCreditsTotal: nil,\n                bonusExpiryDays: nil,\n                resetsAt: nil,\n                updatedAt: Date())\n        }\n\n        // Require at least one key pattern to match to avoid silent failures.\n        // Managed plans without usage data return early above.\n        if !matchedPercent, !matchedCredits {\n            throw KiroStatusProbeError.parseError(\n                \"No recognizable usage patterns found. Kiro CLI output format may have changed.\")\n        }\n\n        return KiroUsageSnapshot(\n            planName: planName,\n            creditsUsed: creditsUsed,\n            creditsTotal: creditsTotal,\n            creditsPercent: creditsPercent,\n            bonusCreditsUsed: bonusUsed,\n            bonusCreditsTotal: bonusTotal,\n            bonusExpiryDays: bonusExpiryDays,\n            resetsAt: resetsAt,\n            updatedAt: Date())\n    }\n\n    private static func stripANSI(_ text: String) -> String {\n        // Remove ANSI escape sequences\n        let pattern = #\"\\x1B\\[[0-9;?]*[A-Za-z]|\\x1B\\].*?\\x07\"#\n        guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else {\n            return text\n        }\n        let range = NSRange(text.startIndex..., in: text)\n        return regex.stringByReplacingMatches(in: text, options: [], range: range, withTemplate: \"\")\n    }\n\n    private static func parseResetDate(_ dateStr: String) -> Date? {\n        // Format: MM/DD - assume current or next year\n        let parts = dateStr.split(separator: \"/\")\n        guard parts.count == 2,\n              let month = Int(parts[0]),\n              let day = Int(parts[1])\n        else { return nil }\n\n        let calendar = Calendar.current\n        let now = Date()\n        let currentYear = calendar.component(.year, from: now)\n\n        var components = DateComponents()\n        components.month = month\n        components.day = day\n        components.year = currentYear\n\n        if let date = calendar.date(from: components), date > now {\n            return date\n        }\n\n        // If the date is in the past, it's next year\n        components.year = currentYear + 1\n        return calendar.date(from: components)\n    }\n\n    private static func isUsageOutputComplete(_ output: String) -> Bool {\n        let stripped = self.stripANSI(output).lowercased()\n        return stripped.contains(\"covered in plan\")\n            || stripped.contains(\"resets on\")\n            || stripped.contains(\"bonus credits\")\n            || stripped.contains(\"plan:\")\n            || stripped.contains(\"managed by admin\")\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/MiniMax/MiniMaxAPIRegion.swift",
    "content": "import Foundation\n\npublic enum MiniMaxAPIRegion: String, CaseIterable, Sendable {\n    case global\n    case chinaMainland = \"cn\"\n\n    private static let codingPlanPath = \"user-center/payment/coding-plan\"\n    private static let codingPlanQuery = \"cycle_type=3\"\n    private static let remainsPath = \"v1/api/openplatform/coding_plan/remains\"\n\n    public var displayName: String {\n        switch self {\n        case .global:\n            \"Global (platform.minimax.io)\"\n        case .chinaMainland:\n            \"China mainland (platform.minimaxi.com)\"\n        }\n    }\n\n    public var baseURLString: String {\n        switch self {\n        case .global:\n            \"https://platform.minimax.io\"\n        case .chinaMainland:\n            \"https://platform.minimaxi.com\"\n        }\n    }\n\n    public var apiBaseURLString: String {\n        switch self {\n        case .global:\n            \"https://api.minimax.io\"\n        case .chinaMainland:\n            \"https://api.minimaxi.com\"\n        }\n    }\n\n    public var codingPlanURL: URL {\n        var components = URLComponents(string: self.baseURLString)!\n        components.path = \"/\" + Self.codingPlanPath\n        components.query = Self.codingPlanQuery\n        return components.url!\n    }\n\n    public var codingPlanRefererURL: URL {\n        var components = URLComponents(string: self.baseURLString)!\n        components.path = \"/\" + Self.codingPlanPath\n        return components.url!\n    }\n\n    public var remainsURL: URL {\n        URL(string: self.baseURLString)!.appendingPathComponent(Self.remainsPath)\n    }\n\n    public var apiRemainsURL: URL {\n        URL(string: self.apiBaseURLString)!.appendingPathComponent(Self.remainsPath)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/MiniMax/MiniMaxAPISettingsReader.swift",
    "content": "import Foundation\n\npublic struct MiniMaxAPISettingsReader: Sendable {\n    public static let apiTokenKey = \"MINIMAX_API_KEY\"\n\n    public enum APIKeyKind: Sendable {\n        case codingPlan\n        case standard\n        case unknown\n    }\n\n    public static func apiToken(\n        environment: [String: String] = ProcessInfo.processInfo.environment) -> String?\n    {\n        if let token = self.cleaned(environment[apiTokenKey]) { return token }\n        return nil\n    }\n\n    public static func apiKeyKind(\n        environment: [String: String] = ProcessInfo.processInfo.environment) -> APIKeyKind?\n    {\n        self.apiKeyKind(token: self.apiToken(environment: environment))\n    }\n\n    public static func apiKeyKind(token: String?) -> APIKeyKind? {\n        guard let cleaned = self.cleaned(token) else { return nil }\n        if cleaned.hasPrefix(\"sk-cp-\") { return .codingPlan }\n        if cleaned.hasPrefix(\"sk-api-\") { return .standard }\n        return .unknown\n    }\n\n    static func cleaned(_ raw: String?) -> String? {\n        guard var value = raw?.trimmingCharacters(in: .whitespacesAndNewlines), !value.isEmpty else {\n            return nil\n        }\n\n        if (value.hasPrefix(\"\\\"\") && value.hasSuffix(\"\\\"\")) ||\n            (value.hasPrefix(\"'\") && value.hasSuffix(\"'\"))\n        {\n            value.removeFirst()\n            value.removeLast()\n        }\n\n        value = value.trimmingCharacters(in: .whitespacesAndNewlines)\n        return value.isEmpty ? nil : value\n    }\n}\n\npublic enum MiniMaxAPISettingsError: LocalizedError, Sendable {\n    case missingToken\n\n    public var errorDescription: String? {\n        switch self {\n        case .missingToken:\n            \"MiniMax API token not found. Set apiKey in ~/.codexbar/config.json or MINIMAX_API_KEY.\"\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/MiniMax/MiniMaxAuthMode.swift",
    "content": "import Foundation\n\npublic enum MiniMaxAuthMode: Sendable {\n    case apiToken\n    case cookie\n    case none\n\n    public static func resolve(apiToken: String?, cookieHeader: String?) -> MiniMaxAuthMode {\n        let cleanedToken = self.cleaned(apiToken)\n        let cleanedCookie = self.cleaned(cookieHeader)\n        if cleanedToken != nil {\n            return .apiToken\n        }\n        if cleanedCookie != nil {\n            return .cookie\n        }\n        return .none\n    }\n\n    public var usesAPIToken: Bool {\n        self == .apiToken\n    }\n\n    public var usesCookie: Bool {\n        self == .cookie\n    }\n\n    public var allowsCookies: Bool {\n        self != .apiToken\n    }\n\n    private static func cleaned(_ raw: String?) -> String? {\n        guard let value = raw?.trimmingCharacters(in: .whitespacesAndNewlines),\n              !value.isEmpty\n        else {\n            return nil\n        }\n        return value\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/MiniMax/MiniMaxCookieHeader.swift",
    "content": "import Foundation\n\npublic struct MiniMaxCookieOverride: Sendable {\n    public let cookieHeader: String\n    public let authorizationToken: String?\n    public let groupID: String?\n\n    public init(cookieHeader: String, authorizationToken: String?, groupID: String?) {\n        self.cookieHeader = cookieHeader\n        self.authorizationToken = authorizationToken\n        self.groupID = groupID\n    }\n}\n\npublic enum MiniMaxCookieHeader {\n    private static let headerPatterns: [String] = [\n        #\"(?i)-H\\s*'Cookie:\\s*([^']+)'\"#,\n        #\"(?i)-H\\s*\\\"Cookie:\\s*([^\\\"]+)\\\"\"#,\n        #\"(?i)\\bcookie:\\s*'([^']+)'\"#,\n        #\"(?i)\\bcookie:\\s*\\\"([^\\\"]+)\\\"\"#,\n        #\"(?i)\\bcookie:\\s*([^\\r\\n]+)\"#,\n        #\"(?i)(?:--cookie|-b)\\s*'([^']+)'\"#,\n        #\"(?i)(?:--cookie|-b)\\s*\\\"([^\\\"]+)\\\"\"#,\n        #\"(?i)(?:--cookie|-b)\\s*([^\\s]+)\"#,\n    ]\n    private static let authorizationPattern = #\"(?i)\\bauthorization:\\s*bearer\\s+([A-Za-z0-9._\\-+=/]+)\"#\n    private static let groupIDPattern = #\"(?i)\\bgroup[_]?id=([0-9]{4,})\"#\n\n    public static func override(from raw: String?) -> MiniMaxCookieOverride? {\n        guard let raw = raw?.trimmingCharacters(in: .whitespacesAndNewlines),\n              !raw.isEmpty\n        else {\n            return nil\n        }\n        guard let cookie = self.normalized(from: raw) else { return nil }\n        let authorizationToken = self.extractFirst(pattern: self.authorizationPattern, text: raw)\n        let groupID = self.extractFirst(pattern: self.groupIDPattern, text: raw)\n        return MiniMaxCookieOverride(\n            cookieHeader: cookie,\n            authorizationToken: authorizationToken,\n            groupID: groupID)\n    }\n\n    public static func normalized(from raw: String?) -> String? {\n        guard var value = raw?.trimmingCharacters(in: .whitespacesAndNewlines), !value.isEmpty else {\n            return nil\n        }\n\n        if let extracted = self.extractHeader(from: value) {\n            value = extracted\n        }\n\n        value = self.stripCookiePrefix(value)\n        value = self.stripWrappingQuotes(value)\n        value = value.trimmingCharacters(in: .whitespacesAndNewlines)\n\n        return value.isEmpty ? nil : value\n    }\n\n    private static func extractHeader(from raw: String) -> String? {\n        for pattern in self.headerPatterns {\n            guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { continue }\n            let range = NSRange(raw.startIndex..<raw.endIndex, in: raw)\n            guard let match = regex.firstMatch(in: raw, options: [], range: range),\n                  match.numberOfRanges >= 2,\n                  let captureRange = Range(match.range(at: 1), in: raw)\n            else {\n                continue\n            }\n            let captured = String(raw[captureRange]).trimmingCharacters(in: .whitespacesAndNewlines)\n            if !captured.isEmpty { return captured }\n        }\n        return nil\n    }\n\n    private static func stripCookiePrefix(_ raw: String) -> String {\n        let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)\n        guard trimmed.lowercased().hasPrefix(\"cookie:\") else { return trimmed }\n        let idx = trimmed.index(trimmed.startIndex, offsetBy: \"cookie:\".count)\n        return String(trimmed[idx...]).trimmingCharacters(in: .whitespacesAndNewlines)\n    }\n\n    private static func stripWrappingQuotes(_ raw: String) -> String {\n        guard raw.count >= 2 else { return raw }\n        if (raw.hasPrefix(\"\\\"\") && raw.hasSuffix(\"\\\"\")) ||\n            (raw.hasPrefix(\"'\") && raw.hasSuffix(\"'\"))\n        {\n            return String(raw.dropFirst().dropLast())\n        }\n        return raw\n    }\n\n    private static func extractFirst(pattern: String, text: String) -> String? {\n        guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { return nil }\n        let range = NSRange(text.startIndex..<text.endIndex, in: text)\n        guard let match = regex.firstMatch(in: text, options: [], range: range),\n              match.numberOfRanges >= 2,\n              let captureRange = Range(match.range(at: 1), in: text)\n        else {\n            return nil\n        }\n        let value = text[captureRange].trimmingCharacters(in: .whitespacesAndNewlines)\n        return value.isEmpty ? nil : String(value)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/MiniMax/MiniMaxCookieImporter.swift",
    "content": "import Foundation\n\n#if os(macOS)\nimport SweetCookieKit\n\nprivate let minimaxCookieImportOrder: BrowserCookieImportOrder =\n    ProviderDefaults.metadata[.minimax]?.browserCookieOrder ?? Browser.defaultImportOrder\n\npublic enum MiniMaxCookieImporter {\n    private static let log = CodexBarLog.logger(LogCategories.minimaxCookie)\n    private static let cookieClient = BrowserCookieClient()\n    private static let cookieDomains = [\n        \"platform.minimax.io\",\n        \"openplatform.minimax.io\",\n        \"minimax.io\",\n        \"platform.minimaxi.com\",\n        \"openplatform.minimaxi.com\",\n        \"minimaxi.com\",\n    ]\n\n    public struct SessionInfo: Sendable {\n        public let cookies: [HTTPCookie]\n        public let sourceLabel: String\n\n        public init(cookies: [HTTPCookie], sourceLabel: String) {\n            self.cookies = cookies\n            self.sourceLabel = sourceLabel\n        }\n\n        public var cookieHeader: String {\n            self.cookies.map { \"\\($0.name)=\\($0.value)\" }.joined(separator: \"; \")\n        }\n    }\n\n    public static func importSessions(\n        browserDetection: BrowserDetection,\n        logger: ((String) -> Void)? = nil) throws -> [SessionInfo]\n    {\n        var sessions: [SessionInfo] = []\n\n        // Filter to cookie-eligible browsers to avoid unnecessary keychain prompts\n        let installedBrowsers = minimaxCookieImportOrder.cookieImportCandidates(using: browserDetection)\n        for browserSource in installedBrowsers {\n            do {\n                let perSource = try self.importSessions(from: browserSource, logger: logger)\n                sessions.append(contentsOf: perSource)\n            } catch {\n                BrowserCookieAccessGate.recordIfNeeded(error)\n                self.emit(\n                    \"\\(browserSource.displayName) cookie import failed: \\(error.localizedDescription)\",\n                    logger: logger)\n            }\n        }\n\n        guard !sessions.isEmpty else {\n            throw MiniMaxCookieImportError.noCookies\n        }\n        return sessions\n    }\n\n    public static func importSessions(\n        from browserSource: Browser,\n        logger: ((String) -> Void)? = nil) throws -> [SessionInfo]\n    {\n        let query = BrowserCookieQuery(domains: self.cookieDomains)\n        let log: (String) -> Void = { msg in self.emit(msg, logger: logger) }\n        let sources = try Self.cookieClient.records(\n            matching: query,\n            in: browserSource,\n            logger: log)\n\n        var sessions: [SessionInfo] = []\n        let grouped = Dictionary(grouping: sources, by: { $0.store.profile.id })\n        let sortedGroups = grouped.values.sorted { lhs, rhs in\n            self.mergedLabel(for: lhs) < self.mergedLabel(for: rhs)\n        }\n\n        for group in sortedGroups where !group.isEmpty {\n            let label = self.mergedLabel(for: group)\n            let mergedRecords = self.mergeRecords(group)\n            guard !mergedRecords.isEmpty else { continue }\n            let httpCookies = BrowserCookieClient.makeHTTPCookies(mergedRecords, origin: query.origin)\n            guard !httpCookies.isEmpty else { continue }\n            log(\"Found \\(httpCookies.count) MiniMax cookies in \\(label)\")\n            log(\"\\(label) cookie names: \\(self.cookieNames(from: httpCookies))\")\n            if let token = httpCookies.first(where: { $0.name == \"HERTZ-SESSION\" })?.value {\n                let hint = token.contains(\".\") ? \"jwt\" : \"opaque\"\n                log(\"\\(label) HERTZ-SESSION: \\(token.count) chars (\\(hint))\")\n            }\n            sessions.append(SessionInfo(cookies: httpCookies, sourceLabel: label))\n        }\n        return sessions\n    }\n\n    public static func importSession(\n        browserDetection: BrowserDetection,\n        logger: ((String) -> Void)? = nil) throws -> SessionInfo\n    {\n        let sessions = try self.importSessions(browserDetection: browserDetection, logger: logger)\n        guard let first = sessions.first else {\n            throw MiniMaxCookieImportError.noCookies\n        }\n        return first\n    }\n\n    public static func hasSession(browserDetection: BrowserDetection, logger: ((String) -> Void)? = nil) -> Bool {\n        do {\n            return try !self.importSessions(browserDetection: browserDetection, logger: logger).isEmpty\n        } catch {\n            return false\n        }\n    }\n\n    private static func cookieNames(from cookies: [HTTPCookie]) -> String {\n        let names = Set(cookies.map { \"\\($0.name)@\\($0.domain)\" }).sorted()\n        return names.joined(separator: \", \")\n    }\n\n    private static func emit(_ message: String, logger: ((String) -> Void)?) {\n        logger?(\"[minimax-cookie] \\(message)\")\n        self.log.debug(message)\n    }\n\n    private static func mergedLabel(for sources: [BrowserCookieStoreRecords]) -> String {\n        guard let base = sources.map(\\.label).min() else {\n            return \"Unknown\"\n        }\n        if base.hasSuffix(\" (Network)\") {\n            return String(base.dropLast(\" (Network)\".count))\n        }\n        return base\n    }\n\n    private static func mergeRecords(_ sources: [BrowserCookieStoreRecords]) -> [BrowserCookieRecord] {\n        let sortedSources = sources.sorted { lhs, rhs in\n            self.storePriority(lhs.store.kind) < self.storePriority(rhs.store.kind)\n        }\n        var mergedByKey: [String: BrowserCookieRecord] = [:]\n        for source in sortedSources {\n            for record in source.records {\n                let key = self.recordKey(record)\n                if let existing = mergedByKey[key] {\n                    if self.shouldReplace(existing: existing, candidate: record) {\n                        mergedByKey[key] = record\n                    }\n                } else {\n                    mergedByKey[key] = record\n                }\n            }\n        }\n        return Array(mergedByKey.values)\n    }\n\n    private static func storePriority(_ kind: BrowserCookieStoreKind) -> Int {\n        switch kind {\n        case .network: 0\n        case .primary: 1\n        case .safari: 2\n        }\n    }\n\n    private static func recordKey(_ record: BrowserCookieRecord) -> String {\n        \"\\(record.name)|\\(record.domain)|\\(record.path)\"\n    }\n\n    private static func shouldReplace(existing: BrowserCookieRecord, candidate: BrowserCookieRecord) -> Bool {\n        switch (existing.expires, candidate.expires) {\n        case let (lhs?, rhs?):\n            rhs > lhs\n        case (nil, .some):\n            true\n        case (.some, nil):\n            false\n        case (nil, nil):\n            false\n        }\n    }\n}\n\nenum MiniMaxCookieImportError: LocalizedError {\n    case noCookies\n\n    var errorDescription: String? {\n        switch self {\n        case .noCookies:\n            \"No MiniMax session cookies found in browsers.\"\n        }\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/MiniMax/MiniMaxLocalStorageImporter.swift",
    "content": "import Foundation\n#if os(macOS)\nimport SweetCookieKit\n#endif\n\n#if os(macOS)\nenum MiniMaxLocalStorageImporter {\n    struct TokenInfo {\n        let accessToken: String\n        let groupID: String?\n        let sourceLabel: String\n    }\n\n    static func importAccessTokens(\n        browserDetection: BrowserDetection,\n        logger: ((String) -> Void)? = nil) -> [TokenInfo]\n    {\n        let log: (String) -> Void = { msg in logger?(\"[minimax-storage] \\(msg)\") }\n        var tokens: [TokenInfo] = []\n\n        let chromeCandidates = self.chromeLocalStorageCandidates(browserDetection: browserDetection)\n        if !chromeCandidates.isEmpty {\n            log(\"Chrome local storage candidates: \\(chromeCandidates.count)\")\n        }\n\n        for candidate in chromeCandidates {\n            guard case let .chromeLevelDB(levelDBURL) = candidate.kind else { continue }\n            let snapshot = self.readLocalStorage(from: levelDBURL, logger: log)\n            if !snapshot.tokens.isEmpty {\n                let groupID = snapshot.groupID ?? self.groupID(fromJWT: snapshot.tokens.first ?? \"\")\n                if groupID != nil {\n                    log(\"Found MiniMax group id in \\(candidate.label)\")\n                }\n                for token in snapshot.tokens {\n                    let hint = token.contains(\".\") ? \"jwt\" : \"opaque\"\n                    log(\"Found MiniMax access token in \\(candidate.label): \\(token.count) chars (\\(hint))\")\n                    tokens.append(TokenInfo(accessToken: token, groupID: groupID, sourceLabel: candidate.label))\n                }\n            }\n        }\n\n        if tokens.isEmpty {\n            let sessionCandidates = self.chromeSessionStorageCandidates(browserDetection: browserDetection)\n            if !sessionCandidates.isEmpty {\n                log(\"Chrome session storage candidates: \\(sessionCandidates.count)\")\n            }\n            for candidate in sessionCandidates {\n                let sessionTokens = self.readSessionStorageTokens(from: candidate.url, logger: log)\n                guard !sessionTokens.isEmpty else { continue }\n                for token in sessionTokens {\n                    let hint = token.contains(\".\") ? \"jwt\" : \"opaque\"\n                    log(\"Found MiniMax access token in \\(candidate.label): \\(token.count) chars (\\(hint))\")\n                    let groupID = self.groupID(fromJWT: token)\n                    tokens.append(TokenInfo(accessToken: token, groupID: groupID, sourceLabel: candidate.label))\n                }\n            }\n        }\n\n        if tokens.isEmpty {\n            let indexedCandidates = self.chromeIndexedDBCandidates(browserDetection: browserDetection)\n            if !indexedCandidates.isEmpty {\n                log(\"Chrome IndexedDB candidates: \\(indexedCandidates.count)\")\n            }\n            for candidate in indexedCandidates {\n                let indexedTokens = self.readIndexedDBTokens(from: candidate.url, logger: log)\n                guard !indexedTokens.isEmpty else { continue }\n                for token in indexedTokens {\n                    let hint = token.contains(\".\") ? \"jwt\" : \"opaque\"\n                    log(\"Found MiniMax access token in \\(candidate.label): \\(token.count) chars (\\(hint))\")\n                    let groupID = self.groupID(fromJWT: token)\n                    tokens.append(TokenInfo(accessToken: token, groupID: groupID, sourceLabel: candidate.label))\n                }\n            }\n        }\n\n        if tokens.isEmpty {\n            log(\"No MiniMax access token found in browser storage\")\n        }\n\n        return tokens\n    }\n\n    static func importGroupIDs(\n        browserDetection: BrowserDetection,\n        logger: ((String) -> Void)? = nil) -> [String: String]\n    {\n        let log: (String) -> Void = { msg in logger?(\"[minimax-storage] \\(msg)\") }\n        var results: [String: String] = [:]\n\n        let chromeCandidates = self.chromeLocalStorageCandidates(browserDetection: browserDetection)\n        if !chromeCandidates.isEmpty {\n            log(\"Chrome local storage candidates: \\(chromeCandidates.count)\")\n        }\n\n        for candidate in chromeCandidates {\n            guard case let .chromeLevelDB(levelDBURL) = candidate.kind else { continue }\n            let snapshot = self.readLocalStorage(from: levelDBURL, logger: log)\n            if let groupID = snapshot.groupID, results[candidate.label] == nil {\n                log(\"Found MiniMax group id in \\(candidate.label)\")\n                results[candidate.label] = groupID\n            }\n        }\n\n        return results\n    }\n\n    // MARK: - Chrome local storage discovery\n\n    private enum LocalStorageSourceKind {\n        case chromeLevelDB(URL)\n    }\n\n    private struct LocalStorageCandidate {\n        let label: String\n        let kind: LocalStorageSourceKind\n    }\n\n    private struct SessionStorageCandidate {\n        let label: String\n        let url: URL\n    }\n\n    private struct IndexedDBCandidate {\n        let label: String\n        let url: URL\n    }\n\n    private static func chromeLocalStorageCandidates(browserDetection: BrowserDetection) -> [LocalStorageCandidate] {\n        let browsers: [Browser] = [\n            .chrome,\n            .chromeBeta,\n            .chromeCanary,\n            .edge,\n            .edgeBeta,\n            .edgeCanary,\n            .brave,\n            .braveBeta,\n            .braveNightly,\n            .vivaldi,\n            .arc,\n            .arcBeta,\n            .arcCanary,\n            .dia,\n            .chatgptAtlas,\n            .chromium,\n            .helium,\n        ]\n\n        // Filter to browsers with profile data to avoid unnecessary filesystem access\n        let installedBrowsers = browsers.browsersWithProfileData(using: browserDetection)\n\n        let roots = ChromiumProfileLocator\n            .roots(for: installedBrowsers, homeDirectories: BrowserCookieClient.defaultHomeDirectories())\n            .map { (url: $0.url, labelPrefix: $0.labelPrefix) }\n\n        var candidates: [LocalStorageCandidate] = []\n        for root in roots {\n            candidates.append(contentsOf: self.chromeProfileLocalStorageDirs(\n                root: root.url,\n                labelPrefix: root.labelPrefix))\n        }\n        return candidates\n    }\n\n    private static func chromeProfileLocalStorageDirs(root: URL, labelPrefix: String) -> [LocalStorageCandidate] {\n        guard let entries = try? FileManager.default.contentsOfDirectory(\n            at: root,\n            includingPropertiesForKeys: [.isDirectoryKey],\n            options: [.skipsHiddenFiles])\n        else { return [] }\n\n        let profileDirs = entries.filter { url in\n            guard let isDir = (try? url.resourceValues(forKeys: [.isDirectoryKey]).isDirectory), isDir else {\n                return false\n            }\n            let name = url.lastPathComponent\n            return name == \"Default\" || name.hasPrefix(\"Profile \") || name.hasPrefix(\"user-\")\n        }\n        .sorted { $0.lastPathComponent < $1.lastPathComponent }\n\n        return profileDirs.compactMap { dir in\n            let levelDBURL = dir.appendingPathComponent(\"Local Storage\").appendingPathComponent(\"leveldb\")\n            guard FileManager.default.fileExists(atPath: levelDBURL.path) else { return nil }\n            let label = \"\\(labelPrefix) \\(dir.lastPathComponent)\"\n            return LocalStorageCandidate(label: label, kind: .chromeLevelDB(levelDBURL))\n        }\n    }\n\n    private static func chromeSessionStorageCandidates(browserDetection: BrowserDetection)\n    -> [SessionStorageCandidate] {\n        let browsers: [Browser] = [\n            .chrome,\n            .chromeBeta,\n            .chromeCanary,\n            .edge,\n            .edgeBeta,\n            .edgeCanary,\n            .brave,\n            .braveBeta,\n            .braveNightly,\n            .vivaldi,\n            .arc,\n            .arcBeta,\n            .arcCanary,\n            .dia,\n            .chatgptAtlas,\n            .chromium,\n            .helium,\n        ]\n\n        // Filter to browsers with profile data to avoid unnecessary filesystem access\n        let installedBrowsers = browsers.browsersWithProfileData(using: browserDetection)\n\n        let roots = ChromiumProfileLocator\n            .roots(for: installedBrowsers, homeDirectories: BrowserCookieClient.defaultHomeDirectories())\n            .map { (url: $0.url, labelPrefix: $0.labelPrefix) }\n\n        var candidates: [SessionStorageCandidate] = []\n        for root in roots {\n            candidates.append(contentsOf: self.chromeProfileSessionStorageDirs(\n                root: root.url,\n                labelPrefix: root.labelPrefix))\n        }\n        return candidates\n    }\n\n    private static func chromeProfileSessionStorageDirs(root: URL, labelPrefix: String) -> [SessionStorageCandidate] {\n        guard let entries = try? FileManager.default.contentsOfDirectory(\n            at: root,\n            includingPropertiesForKeys: [.isDirectoryKey],\n            options: [.skipsHiddenFiles])\n        else { return [] }\n\n        let profileDirs = entries.filter { url in\n            guard let isDir = (try? url.resourceValues(forKeys: [.isDirectoryKey]).isDirectory), isDir else {\n                return false\n            }\n            let name = url.lastPathComponent\n            return name == \"Default\" || name.hasPrefix(\"Profile \") || name.hasPrefix(\"user-\")\n        }\n        .sorted { $0.lastPathComponent < $1.lastPathComponent }\n\n        return profileDirs.compactMap { dir in\n            let sessionURL = dir.appendingPathComponent(\"Session Storage\")\n            guard FileManager.default.fileExists(atPath: sessionURL.path) else { return nil }\n            let label = \"\\(labelPrefix) \\(dir.lastPathComponent) (Session Storage)\"\n            return SessionStorageCandidate(label: label, url: sessionURL)\n        }\n    }\n\n    private static func chromeIndexedDBCandidates(browserDetection: BrowserDetection) -> [IndexedDBCandidate] {\n        let browsers: [Browser] = [\n            .chrome,\n            .chromeBeta,\n            .chromeCanary,\n            .edge,\n            .edgeBeta,\n            .edgeCanary,\n            .brave,\n            .braveBeta,\n            .braveNightly,\n            .vivaldi,\n            .arc,\n            .arcBeta,\n            .arcCanary,\n            .dia,\n            .chatgptAtlas,\n            .chromium,\n            .helium,\n        ]\n\n        // Filter to browsers with profile data to avoid unnecessary filesystem access\n        let installedBrowsers = browsers.browsersWithProfileData(using: browserDetection)\n\n        let roots = ChromiumProfileLocator\n            .roots(for: installedBrowsers, homeDirectories: BrowserCookieClient.defaultHomeDirectories())\n            .map { (url: $0.url, labelPrefix: $0.labelPrefix) }\n\n        var candidates: [IndexedDBCandidate] = []\n        for root in roots {\n            candidates.append(contentsOf: self.chromeProfileIndexedDBDirs(\n                root: root.url,\n                labelPrefix: root.labelPrefix))\n        }\n        return candidates\n    }\n\n    private static func chromeProfileIndexedDBDirs(root: URL, labelPrefix: String) -> [IndexedDBCandidate] {\n        guard let entries = try? FileManager.default.contentsOfDirectory(\n            at: root,\n            includingPropertiesForKeys: [.isDirectoryKey],\n            options: [.skipsHiddenFiles])\n        else { return [] }\n\n        let profileDirs = entries.filter { url in\n            guard let isDir = (try? url.resourceValues(forKeys: [.isDirectoryKey]).isDirectory), isDir else {\n                return false\n            }\n            let name = url.lastPathComponent\n            return name == \"Default\" || name.hasPrefix(\"Profile \") || name.hasPrefix(\"user-\")\n        }\n        .sorted { $0.lastPathComponent < $1.lastPathComponent }\n\n        let targetPrefixes = [\n            \"https_platform.minimax.io_\",\n            \"https_www.minimax.io_\",\n            \"https_minimax.io_\",\n            \"https_platform.minimaxi.com_\",\n            \"https_minimaxi.com_\",\n            \"https_www.minimaxi.com_\",\n        ]\n\n        var candidates: [IndexedDBCandidate] = []\n        for dir in profileDirs {\n            let indexedDBRoot = dir.appendingPathComponent(\"IndexedDB\")\n            guard let dbEntries = try? FileManager.default.contentsOfDirectory(\n                at: indexedDBRoot,\n                includingPropertiesForKeys: [.isDirectoryKey],\n                options: [.skipsHiddenFiles])\n            else { continue }\n            for entry in dbEntries {\n                guard let isDir = (try? entry.resourceValues(forKeys: [.isDirectoryKey]).isDirectory), isDir else {\n                    continue\n                }\n                let name = entry.lastPathComponent\n                guard targetPrefixes.contains(where: { name.hasPrefix($0) }),\n                      name.hasSuffix(\".indexeddb.leveldb\")\n                else { continue }\n                let label = \"\\(labelPrefix) \\(dir.lastPathComponent) (IndexedDB)\"\n                candidates.append(IndexedDBCandidate(label: label, url: entry))\n            }\n        }\n        return candidates\n    }\n\n    // MARK: - Token extraction\n\n    private struct LocalStorageSnapshot {\n        let tokens: [String]\n        let groupID: String?\n    }\n\n    private static func readLocalStorage(\n        from levelDBURL: URL,\n        logger: ((String) -> Void)? = nil) -> LocalStorageSnapshot\n    {\n        let origins = [\n            \"https://platform.minimax.io\",\n            \"https://www.minimax.io\",\n            \"https://minimax.io\",\n            \"https://platform.minimaxi.com\",\n            \"https://www.minimaxi.com\",\n            \"https://minimaxi.com\",\n        ]\n        var entries: [SweetCookieKit.ChromiumLocalStorageEntry] = []\n        for origin in origins {\n            entries.append(contentsOf: SweetCookieKit.ChromiumLocalStorageReader.readEntries(\n                for: origin,\n                in: levelDBURL,\n                logger: logger))\n        }\n\n        var tokens: [String] = []\n        var seen = Set<String>()\n        var groupID: String?\n        var hasMinimaxSignal = !entries.isEmpty\n\n        for entry in entries {\n            let extracted = self.extractAccessTokens(from: entry.value)\n            for token in extracted where !seen.contains(token) {\n                seen.insert(token)\n                tokens.append(token)\n            }\n            if groupID == nil, let match = self.extractGroupID(from: entry.value) {\n                groupID = match\n            }\n        }\n\n        if tokens.isEmpty {\n            let textEntries = SweetCookieKit.ChromiumLocalStorageReader.readTextEntries(\n                in: levelDBURL,\n                logger: logger)\n            let candidateEntries = textEntries.filter { entry in\n                let key = entry.key.lowercased()\n                let value = entry.value.lowercased()\n                return key.contains(\"minimax.io\") || value.contains(\"minimax.io\") ||\n                    key.contains(\"minimaxi.com\") || value.contains(\"minimaxi.com\")\n            }\n            if !candidateEntries.isEmpty {\n                logger?(\"[minimax-storage] Local storage text entries: \\(candidateEntries.count)\")\n                hasMinimaxSignal = true\n            }\n            for entry in candidateEntries {\n                let extracted = self.extractAccessTokens(from: entry.value)\n                for token in extracted where !seen.contains(token) {\n                    if token.contains(\".\"), !self.isMiniMaxJWT(token) {\n                        continue\n                    }\n                    seen.insert(token)\n                    tokens.append(token)\n                }\n                if groupID == nil, let match = self.extractGroupID(from: entry.value) {\n                    groupID = match\n                }\n            }\n        }\n\n        if tokens.isEmpty, hasMinimaxSignal {\n            let rawCandidates = SweetCookieKit.ChromiumLocalStorageReader.readTokenCandidates(\n                in: levelDBURL,\n                minimumLength: 60,\n                logger: logger)\n            if !rawCandidates.isEmpty {\n                logger?(\"[minimax-storage] Local storage raw token candidates: \\(rawCandidates.count)\")\n            }\n            for candidate in rawCandidates\n                where self.looksLikeToken(candidate) && self.isMiniMaxJWT(candidate) && !seen.contains(candidate)\n            {\n                seen.insert(candidate)\n                tokens.append(candidate)\n                if groupID == nil {\n                    groupID = self.groupID(fromJWT: candidate)\n                }\n            }\n        }\n\n        if tokens.isEmpty, !entries.isEmpty {\n            let sample = entries.prefix(6).map { \"\\($0.key) (\\($0.value.count) chars)\" }\n            logger?(\"[minimax-storage] Local storage key sample: \\(sample.joined(separator: \", \"))\")\n            for entry in entries\n                where entry.key == \"user_detail\" || entry.key == \"persist:root\" || entry.key == \"access_token\"\n            {\n                let preview = entry.value.prefix(200)\n                if entry.key == \"access_token\" {\n                    logger?(\"[minimax-storage] \\(entry.key) preview: \\(preview) (raw \\(entry.rawValueLength) bytes)\")\n                } else {\n                    logger?(\"[minimax-storage] \\(entry.key) preview: \\(preview)\")\n                }\n                if entry.key == \"persist:root\" {\n                    self.logPersistRootKeys(entry.value, logger: logger)\n                }\n            }\n        }\n\n        return LocalStorageSnapshot(tokens: tokens, groupID: groupID)\n    }\n\n    private static func readSessionStorageTokens(\n        from levelDBURL: URL,\n        logger: ((String) -> Void)? = nil) -> [String]\n    {\n        let entries = SweetCookieKit.ChromiumLocalStorageReader.readTextEntries(\n            in: levelDBURL,\n            logger: logger)\n        guard !entries.isEmpty else { return [] }\n\n        let origins = [\n            \"https://platform.minimax.io\",\n            \"https://www.minimax.io\",\n            \"https://minimax.io\",\n            \"https://platform.minimaxi.com\",\n            \"https://www.minimaxi.com\",\n            \"https://minimaxi.com\",\n        ]\n        let mapIDs = self.sessionStorageMapIDs(in: entries, origins: origins, logger: logger)\n        if mapIDs.isEmpty {\n            logger?(\"[minimax-storage] No MiniMax session storage namespaces found\")\n            return []\n        }\n\n        let mapEntries = entries.filter { entry in\n            guard let mapID = self.sessionStorageMapID(fromKey: entry.key) else { return false }\n            return mapIDs.contains(mapID)\n        }\n        if mapEntries.isEmpty {\n            logger?(\"[minimax-storage] No MiniMax session storage map entries found\")\n            return []\n        }\n\n        let tokenKeySample = mapEntries\n            .filter { entry in\n                entry.key.localizedCaseInsensitiveContains(\"token\") ||\n                    entry.key.localizedCaseInsensitiveContains(\"auth\")\n            }\n            .prefix(8)\n            .map(\\.key)\n        if !tokenKeySample.isEmpty {\n            logger?(\"[minimax-storage] MiniMax session storage token keys: \\(tokenKeySample.joined(separator: \", \"))\")\n        }\n\n        var tokens: [String] = []\n        var seen = Set<String>()\n        for entry in mapEntries {\n            let extracted = self.extractAccessTokens(from: entry.value)\n            for token in extracted where !seen.contains(token) {\n                seen.insert(token)\n                tokens.append(token)\n            }\n        }\n\n        if tokens.isEmpty {\n            let sample = mapEntries.prefix(8).map { entry in\n                \"\\(entry.key) (\\(entry.value.count) chars)\"\n            }\n            logger?(\"[minimax-storage] MiniMax session storage sample: \\(sample.joined(separator: \", \"))\")\n        }\n\n        return tokens\n    }\n\n    private static func readIndexedDBTokens(\n        from levelDBURL: URL,\n        logger: ((String) -> Void)? = nil) -> [String]\n    {\n        let entries = SweetCookieKit.ChromiumLocalStorageReader.readTextEntries(\n            in: levelDBURL,\n            logger: logger)\n        var tokens: [String] = []\n        var seen = Set<String>()\n        if !entries.isEmpty {\n            for entry in entries {\n                let extracted = self.extractAccessTokens(from: entry.value)\n                for token in extracted where !seen.contains(token) {\n                    seen.insert(token)\n                    tokens.append(token)\n                }\n            }\n        }\n\n        if tokens.isEmpty {\n            let rawCandidates = SweetCookieKit.ChromiumLocalStorageReader.readTokenCandidates(\n                in: levelDBURL,\n                minimumLength: 60,\n                logger: logger)\n            if !rawCandidates.isEmpty {\n                logger?(\"[minimax-storage] IndexedDB raw token candidates: \\(rawCandidates.count)\")\n            }\n            for candidate in rawCandidates where self.looksLikeToken(candidate) && !seen.contains(candidate) {\n                seen.insert(candidate)\n                tokens.append(candidate)\n            }\n        }\n\n        if tokens.isEmpty {\n            let sample = entries.prefix(8).map { entry in\n                \"\\(entry.key) (\\(entry.value.count) chars)\"\n            }\n            if !sample.isEmpty {\n                logger?(\"[minimax-storage] IndexedDB sample: \\(sample.joined(separator: \", \"))\")\n            }\n        }\n        return tokens\n    }\n\n    private static func sessionStorageMapIDs(\n        in entries: [SweetCookieKit.ChromiumLevelDBTextEntry],\n        origins: [String],\n        logger: ((String) -> Void)? = nil) -> Set<Int>\n    {\n        var mapIDs = Set<Int>()\n        for entry in entries where entry.key.hasPrefix(\"namespace-\") {\n            for origin in origins where entry.key.localizedCaseInsensitiveContains(origin) {\n                if let mapID = self.sessionStorageMapID(fromValue: entry.value) {\n                    mapIDs.insert(mapID)\n                }\n            }\n        }\n        if !mapIDs.isEmpty {\n            let values = mapIDs.map(String.init).sorted().joined(separator: \", \")\n            logger?(\"[minimax-storage] Session storage map ids for MiniMax: \\(values)\")\n        }\n        return mapIDs\n    }\n\n    private static func sessionStorageMapID(fromValue value: String) -> Int? {\n        let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)\n        return Int(trimmed)\n    }\n\n    private static func sessionStorageMapID(fromKey key: String) -> Int? {\n        guard key.hasPrefix(\"map-\") else { return nil }\n        let parts = key.split(separator: \"-\", maxSplits: 2)\n        guard parts.count >= 2 else { return nil }\n        return Int(parts[1])\n    }\n\n    private static func extractAccessTokens(from value: String) -> [String] {\n        var tokens = Set<String>()\n\n        let patterns = [\n            #\"access_token[^A-Za-z0-9._\\-+=/]+([A-Za-z0-9._\\-+=/]{20,})\"#,\n            #\"accessToken[^A-Za-z0-9._\\-+=/]+([A-Za-z0-9._\\-+=/]{20,})\"#,\n            #\"id_token[^A-Za-z0-9._\\-+=/]+([A-Za-z0-9._\\-+=/]{20,})\"#,\n            #\"idToken[^A-Za-z0-9._\\-+=/]+([A-Za-z0-9._\\-+=/]{20,})\"#,\n        ]\n\n        for pattern in patterns {\n            guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { continue }\n            let range = NSRange(value.startIndex..<value.endIndex, in: value)\n            let matches = regex.matches(in: value, options: [], range: range)\n            for match in matches {\n                guard match.numberOfRanges > 1,\n                      let tokenRange = Range(match.range(at: 1), in: value)\n                else { continue }\n                tokens.insert(String(value[tokenRange]))\n            }\n        }\n\n        if let jsonTokens = self.extractTokensFromJSON(value) {\n            tokens.formUnion(jsonTokens)\n        }\n\n        if let jwtMatches = self.matchJWTs(in: value) {\n            tokens.formUnion(jwtMatches)\n        }\n\n        let preferred = tokens.filter { $0.count >= 60 }\n        return preferred.isEmpty ? Array(tokens) : Array(preferred)\n    }\n\n    private static func extractTokensFromJSON(_ value: String) -> [String]? {\n        guard let data = value.data(using: .utf8),\n              let json = try? JSONSerialization.jsonObject(with: data, options: [])\n        else { return nil }\n        return self.collectTokens(from: json)\n    }\n\n    private static func collectTokens(from value: Any) -> [String] {\n        var tokens: [String] = []\n        switch value {\n        case let dict as [String: Any]:\n            for (key, child) in dict {\n                if self.tokenKeys.contains(key), let string = child as? String, self.looksLikeToken(string) {\n                    tokens.append(string)\n                } else {\n                    tokens.append(contentsOf: self.collectTokens(from: child))\n                }\n            }\n        case let array as [Any]:\n            for child in array {\n                tokens.append(contentsOf: self.collectTokens(from: child))\n            }\n        case let string as String:\n            if self.looksLikeToken(string) {\n                tokens.append(string)\n            } else if let nested = self.extractTokensFromJSON(string) {\n                tokens.append(contentsOf: nested)\n            }\n        default:\n            break\n        }\n        return tokens\n    }\n\n    private static let tokenKeys: Set<String> = [\n        \"access_token\",\n        \"accessToken\",\n        \"id_token\",\n        \"idToken\",\n        \"token\",\n        \"authToken\",\n        \"authorization\",\n        \"bearer\",\n    ]\n\n    private static func looksLikeToken(_ value: String) -> Bool {\n        let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)\n        if trimmed.contains(\".\"), trimmed.split(separator: \".\").count >= 3 {\n            return trimmed.count >= 60\n        }\n        return trimmed.count >= 60 && trimmed.range(of: #\"^[A-Za-z0-9._\\-+=/]+$\"#, options: .regularExpression) != nil\n    }\n\n    private static func logPersistRootKeys(_ value: String, logger: ((String) -> Void)?) {\n        guard let data = value.data(using: .utf8),\n              let json = try? JSONSerialization.jsonObject(with: data, options: []),\n              let root = json as? [String: Any]\n        else { return }\n\n        let rootKeys = root.keys.sorted()\n        if !rootKeys.isEmpty {\n            let sample = rootKeys.prefix(10).joined(separator: \", \")\n            logger?(\"[minimax-storage] persist:root keys: \\(sample)\")\n        }\n\n        if let authString = root[\"auth\"] as? String,\n           let authData = authString.data(using: .utf8),\n           let authJSON = try? JSONSerialization.jsonObject(with: authData, options: []),\n           let auth = authJSON as? [String: Any]\n        {\n            let authKeys = auth.keys.sorted()\n            if !authKeys.isEmpty {\n                let sample = authKeys.prefix(10).joined(separator: \", \")\n                logger?(\"[minimax-storage] persist:root auth keys: \\(sample)\")\n            }\n        }\n    }\n\n    private static func matchJWTs(in value: String) -> [String]? {\n        let pattern = #\"[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,}\"#\n        guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { return nil }\n        let range = NSRange(value.startIndex..<value.endIndex, in: value)\n        let matches = regex.matches(in: value, options: [], range: range)\n        guard !matches.isEmpty else { return nil }\n        return matches.compactMap { match in\n            guard let tokenRange = Range(match.range(at: 0), in: value) else { return nil }\n            return String(value[tokenRange])\n        }\n    }\n\n    private static func isMiniMaxJWT(_ token: String) -> Bool {\n        guard let claims = self.decodeJWTClaims(token) else { return false }\n        if let iss = claims[\"iss\"] as? String, iss.localizedCaseInsensitiveContains(\"minimax\") {\n            return true\n        }\n        let signalKeys = [\n            \"GroupID\",\n            \"GroupName\",\n            \"UserName\",\n            \"SubjectID\",\n            \"Mail\",\n            \"TokenType\",\n        ]\n        if signalKeys.contains(where: { claims[$0] != nil }) {\n            return true\n        }\n        return false\n    }\n\n    private static func extractGroupID(from value: String) -> String? {\n        if let data = value.data(using: .utf8),\n           let json = try? JSONSerialization.jsonObject(with: data, options: []),\n           let match = self.extractGroupID(from: json)\n        {\n            return match\n        }\n\n        let markers = [\n            \"groups\\\":[\",\n            \"groupId\\\":\\\"\",\n            \"group_id\\\":\\\"\",\n        ]\n        for marker in markers {\n            guard let range = value.range(of: marker) else { continue }\n            let tail = value[range.upperBound...].prefix(200)\n            if let match = self.longestDigitSequence(in: String(tail)) {\n                return match\n            }\n        }\n        return nil\n    }\n\n    private static func extractGroupID(from value: Any) -> String? {\n        switch value {\n        case let dict as [String: Any]:\n            for (key, child) in dict {\n                if key.lowercased().contains(\"group\"),\n                   let match = self.stringID(from: child)\n                {\n                    return match\n                }\n                if let nested = self.extractGroupID(from: child) {\n                    return nested\n                }\n            }\n        case let array as [Any]:\n            for child in array {\n                if let match = self.extractGroupID(from: child) {\n                    return match\n                }\n            }\n        default:\n            break\n        }\n        return nil\n    }\n\n    private static func groupID(fromJWT token: String) -> String? {\n        guard token.contains(\".\") else { return nil }\n        guard let claims = self.decodeJWTClaims(token) else { return nil }\n\n        let directKeys = [\n            \"group_id\",\n            \"groupId\",\n            \"groupID\",\n            \"gid\",\n            \"tenant_id\",\n            \"tenantId\",\n            \"org_id\",\n            \"orgId\",\n        ]\n        for key in directKeys {\n            if let match = self.stringID(from: claims[key]) {\n                return match\n            }\n        }\n\n        for (key, value) in claims where key.lowercased().contains(\"group\") {\n            if let match = self.stringID(from: value) {\n                return match\n            }\n        }\n\n        return nil\n    }\n\n    private static func decodeJWTClaims(_ token: String) -> [String: Any]? {\n        let parts = token.split(separator: \".\")\n        guard parts.count >= 2 else { return nil }\n        let payload = String(parts[1])\n        guard let data = self.base64URLDecode(payload) else { return nil }\n        guard let object = try? JSONSerialization.jsonObject(with: data, options: []),\n              let dict = object as? [String: Any]\n        else { return nil }\n        return dict\n    }\n\n    private static func base64URLDecode(_ value: String) -> Data? {\n        var base64 = value.replacingOccurrences(of: \"-\", with: \"+\")\n            .replacingOccurrences(of: \"_\", with: \"/\")\n        let padding = (4 - base64.count % 4) % 4\n        if padding > 0 {\n            base64.append(String(repeating: \"=\", count: padding))\n        }\n        return Data(base64Encoded: base64)\n    }\n\n    private static func stringID(from value: Any?) -> String? {\n        switch value {\n        case let number as Int:\n            return String(number)\n        case let number as Int64:\n            return String(number)\n        case let number as NSNumber:\n            return String(number.intValue)\n        case let text as String:\n            let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)\n            if let match = self.longestDigitSequence(in: trimmed) {\n                return match\n            }\n            return trimmed.isEmpty ? nil : trimmed\n        default:\n            return nil\n        }\n    }\n\n    private static func longestDigitSequence(in text: String) -> String? {\n        guard let regex = try? NSRegularExpression(pattern: #\"[0-9]{4,}\"#, options: []) else { return nil }\n        let range = NSRange(text.startIndex..<text.endIndex, in: text)\n        let matches = regex.matches(in: text, options: [], range: range)\n        let candidates = matches.compactMap { match -> String? in\n            guard let tokenRange = Range(match.range(at: 0), in: text) else { return nil }\n            return String(text[tokenRange])\n        }\n        return candidates.max(by: { $0.count < $1.count })\n    }\n}\n#endif\n\n#if DEBUG && os(macOS)\nextension MiniMaxLocalStorageImporter {\n    static func _extractAccessTokensForTesting(_ value: String) -> [String] {\n        self.extractAccessTokens(from: value)\n    }\n\n    static func _extractGroupIDForTesting(_ value: String) -> String? {\n        self.extractGroupID(from: value)\n    }\n\n    static func _groupIDFromJWTForTesting(_ token: String) -> String? {\n        self.groupID(fromJWT: token)\n    }\n\n    static func _isMiniMaxJWTForTesting(_ token: String) -> Bool {\n        self.isMiniMaxJWT(token)\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/MiniMax/MiniMaxProviderDescriptor.swift",
    "content": "import CodexBarMacroSupport\nimport Foundation\n\n@ProviderDescriptorRegistration\n@ProviderDescriptorDefinition\npublic enum MiniMaxProviderDescriptor {\n    static func makeDescriptor() -> ProviderDescriptor {\n        ProviderDescriptor(\n            id: .minimax,\n            metadata: ProviderMetadata(\n                id: .minimax,\n                displayName: \"MiniMax\",\n                sessionLabel: \"Prompts\",\n                weeklyLabel: \"Window\",\n                opusLabel: nil,\n                supportsOpus: false,\n                supportsCredits: false,\n                creditsHint: \"\",\n                toggleTitle: \"Show MiniMax usage\",\n                cliName: \"minimax\",\n                defaultEnabled: false,\n                isPrimaryProvider: false,\n                usesAccountFallback: false,\n                browserCookieOrder: ProviderBrowserCookieDefaults.defaultImportOrder,\n                dashboardURL: \"https://platform.minimax.io/user-center/payment/coding-plan?cycle_type=3\",\n                statusPageURL: nil),\n            branding: ProviderBranding(\n                iconStyle: .minimax,\n                iconResourceName: \"ProviderIcon-minimax\",\n                color: ProviderColor(red: 254 / 255, green: 96 / 255, blue: 60 / 255)),\n            tokenCost: ProviderTokenCostConfig(\n                supportsTokenCost: false,\n                noDataMessage: { \"MiniMax cost summary is not supported.\" }),\n            fetchPlan: ProviderFetchPlan(\n                sourceModes: [.auto, .web, .api],\n                pipeline: ProviderFetchPipeline(resolveStrategies: self.resolveStrategies)),\n            cli: ProviderCLIConfig(\n                name: \"minimax\",\n                aliases: [\"mini-max\"],\n                versionDetector: nil))\n    }\n\n    private static func resolveStrategies(context: ProviderFetchContext) async -> [any ProviderFetchStrategy] {\n        switch context.sourceMode {\n        case .web:\n            return [MiniMaxCodingPlanFetchStrategy()]\n        case .api:\n            return [MiniMaxAPIFetchStrategy()]\n        case .cli, .oauth:\n            return []\n        case .auto:\n            break\n        }\n        let apiToken = ProviderTokenResolver.minimaxToken(environment: context.env)\n        let apiKeyKind = MiniMaxAPISettingsReader.apiKeyKind(token: apiToken)\n        let authMode = MiniMaxAuthMode.resolve(\n            apiToken: apiToken,\n            cookieHeader: ProviderTokenResolver.minimaxCookie(environment: context.env))\n        if authMode.usesAPIToken {\n            if apiKeyKind == .standard {\n                return [MiniMaxCodingPlanFetchStrategy()]\n            }\n            return [MiniMaxAPIFetchStrategy(), MiniMaxCodingPlanFetchStrategy()]\n        }\n        return [MiniMaxCodingPlanFetchStrategy()]\n    }\n}\n\nstruct MiniMaxAPIFetchStrategy: ProviderFetchStrategy {\n    let id: String = \"minimax.api\"\n    let kind: ProviderFetchKind = .apiToken\n\n    func isAvailable(_ context: ProviderFetchContext) async -> Bool {\n        let authMode = MiniMaxAuthMode.resolve(\n            apiToken: ProviderTokenResolver.minimaxToken(environment: context.env),\n            cookieHeader: ProviderTokenResolver.minimaxCookie(environment: context.env))\n        if let kind = MiniMaxAPISettingsReader.apiKeyKind(environment: context.env),\n           kind == .standard\n        {\n            return false\n        }\n        return authMode.usesAPIToken\n    }\n\n    func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {\n        guard let apiToken = ProviderTokenResolver.minimaxToken(environment: context.env) else {\n            throw MiniMaxAPISettingsError.missingToken\n        }\n        let region = context.settings?.minimax?.apiRegion ?? .global\n        let usage = try await MiniMaxUsageFetcher.fetchUsage(apiToken: apiToken, region: region)\n        return self.makeResult(\n            usage: usage.toUsageSnapshot(),\n            sourceLabel: \"api\")\n    }\n\n    func shouldFallback(on error: Error, context _: ProviderFetchContext) -> Bool {\n        guard let minimaxError = error as? MiniMaxUsageError else { return false }\n        switch minimaxError {\n        case .invalidCredentials:\n            return true\n        case let .apiError(message):\n            return message.contains(\"HTTP 404\")\n        case .networkError, .parseFailed:\n            return false\n        }\n    }\n}\n\nstruct MiniMaxCodingPlanFetchStrategy: ProviderFetchStrategy {\n    let id: String = \"minimax.web\"\n    let kind: ProviderFetchKind = .web\n    private static let log = CodexBarLog.logger(LogCategories.minimaxWeb)\n\n    func isAvailable(_ context: ProviderFetchContext) async -> Bool {\n        if Self.resolveCookieOverride(context: context) != nil {\n            return true\n        }\n        #if os(macOS)\n        if let cached = CookieHeaderCache.load(provider: .minimax),\n           !cached.cookieHeader.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty\n        {\n            return true\n        }\n        return MiniMaxCookieImporter.hasSession(browserDetection: context.browserDetection)\n        #else\n        return false\n        #endif\n    }\n\n    func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {\n        let fetchContext = FetchContext(\n            region: context.settings?.minimax?.apiRegion ?? .global,\n            environment: context.env)\n        if let override = Self.resolveCookieOverride(context: context) {\n            Self.log.debug(\"Using MiniMax cookie header from settings/env\")\n            let snapshot = try await MiniMaxUsageFetcher.fetchUsage(\n                cookieHeader: override.cookieHeader,\n                authorizationToken: override.authorizationToken,\n                groupID: override.groupID,\n                region: fetchContext.region,\n                environment: fetchContext.environment)\n            return self.makeResult(\n                usage: snapshot.toUsageSnapshot(),\n                sourceLabel: \"web\")\n        }\n\n        #if os(macOS)\n        let tokenContext = Self.loadTokenContext(browserDetection: context.browserDetection)\n\n        var lastError: Error?\n        if let cached = CookieHeaderCache.load(provider: .minimax),\n           !cached.cookieHeader.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty\n        {\n            switch await Self.attemptFetch(\n                cookieHeader: cached.cookieHeader,\n                sourceLabel: cached.sourceLabel,\n                tokenContext: tokenContext,\n                logLabel: \"cached\",\n                fetchContext: fetchContext)\n            {\n            case let .success(snapshot):\n                return self.makeResult(\n                    usage: snapshot.toUsageSnapshot(),\n                    sourceLabel: \"web\")\n            case let .failure(error):\n                lastError = error\n                if Self.shouldTryNextBrowser(for: error) {\n                    CookieHeaderCache.clear(provider: .minimax)\n                } else {\n                    throw error\n                }\n            }\n        }\n\n        let sessions = (try? MiniMaxCookieImporter.importSessions(\n            browserDetection: context.browserDetection)) ?? []\n        guard !sessions.isEmpty else {\n            if let lastError { throw lastError }\n            throw MiniMaxSettingsError.missingCookie\n        }\n\n        for session in sessions {\n            switch await Self.attemptFetch(\n                cookieHeader: session.cookieHeader,\n                sourceLabel: session.sourceLabel,\n                tokenContext: tokenContext,\n                logLabel: \"\",\n                fetchContext: fetchContext)\n            {\n            case let .success(snapshot):\n                CookieHeaderCache.store(\n                    provider: .minimax,\n                    cookieHeader: session.cookieHeader,\n                    sourceLabel: session.sourceLabel)\n                return self.makeResult(\n                    usage: snapshot.toUsageSnapshot(),\n                    sourceLabel: \"web\")\n            case let .failure(error):\n                lastError = error\n                if Self.shouldTryNextBrowser(for: error) {\n                    Self.log.debug(\"MiniMax cookies invalid from \\(session.sourceLabel), trying next browser\")\n                    continue\n                }\n                throw error\n            }\n        }\n\n        if let lastError {\n            throw lastError\n        }\n        #endif\n\n        throw MiniMaxSettingsError.missingCookie\n    }\n\n    func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool {\n        false\n    }\n\n    private struct TokenContext {\n        let tokensByLabel: [String: [String]]\n        let groupIDByLabel: [String: String]\n    }\n\n    private struct FetchContext {\n        let region: MiniMaxAPIRegion\n        let environment: [String: String]\n    }\n\n    private enum FetchAttemptResult {\n        case success(MiniMaxUsageSnapshot)\n        case failure(Error)\n    }\n\n    private static func resolveCookieOverride(context: ProviderFetchContext) -> MiniMaxCookieOverride? {\n        if let settings = context.settings?.minimax {\n            guard settings.cookieSource == .manual else { return nil }\n            return MiniMaxCookieHeader.override(from: settings.manualCookieHeader)\n        }\n        guard let raw = ProviderTokenResolver.minimaxCookie(environment: context.env) else {\n            return nil\n        }\n        return MiniMaxCookieHeader.override(from: raw)\n    }\n\n    private static func normalizeStorageLabel(_ label: String) -> String {\n        let suffixes = [\" (Session Storage)\", \" (IndexedDB)\"]\n        for suffix in suffixes where label.hasSuffix(suffix) {\n            return String(label.dropLast(suffix.count))\n        }\n        return label\n    }\n\n    private static func loadTokenContext(browserDetection: BrowserDetection) -> TokenContext {\n        #if os(macOS)\n        let tokenLog: (String) -> Void = { msg in Self.log.debug(msg) }\n        let accessTokens = MiniMaxLocalStorageImporter.importAccessTokens(\n            browserDetection: browserDetection,\n            logger: tokenLog)\n        let groupIDs = MiniMaxLocalStorageImporter.importGroupIDs(\n            browserDetection: browserDetection,\n            logger: tokenLog)\n        var tokensByLabel: [String: [String]] = [:]\n        var groupIDByLabel: [String: String] = [:]\n        for token in accessTokens {\n            let normalized = Self.normalizeStorageLabel(token.sourceLabel)\n            tokensByLabel[normalized, default: []].append(token.accessToken)\n            if let groupID = token.groupID, groupIDByLabel[normalized] == nil {\n                groupIDByLabel[normalized] = groupID\n            }\n        }\n        for (label, groupID) in groupIDs {\n            let normalized = Self.normalizeStorageLabel(label)\n            if groupIDByLabel[normalized] == nil {\n                groupIDByLabel[normalized] = groupID\n            }\n        }\n        return TokenContext(tokensByLabel: tokensByLabel, groupIDByLabel: groupIDByLabel)\n        #else\n        _ = browserDetection\n        return TokenContext(tokensByLabel: [:], groupIDByLabel: [:])\n        #endif\n    }\n\n    private static func attemptFetch(\n        cookieHeader: String,\n        sourceLabel: String,\n        tokenContext: TokenContext,\n        logLabel: String,\n        fetchContext: FetchContext) async -> FetchAttemptResult\n    {\n        let normalizedLabel = Self.normalizeStorageLabel(sourceLabel)\n        let tokenCandidates = tokenContext.tokensByLabel[normalizedLabel] ?? []\n        let groupID = tokenContext.groupIDByLabel[normalizedLabel]\n        let cookieToken = Self.cookieValue(named: \"HERTZ-SESSION\", in: cookieHeader)\n        var attempts: [String?] = tokenCandidates.map(\\.self)\n        if let cookieToken, !tokenCandidates.contains(cookieToken) {\n            attempts.append(cookieToken)\n        }\n        attempts.append(nil)\n\n        let prefix = logLabel.isEmpty ? \"\" : \"\\(logLabel) \"\n        var lastError: Error?\n        for token in attempts {\n            let tokenLabel: String = {\n                guard let token else { return \"\" }\n                if token == cookieToken { return \" + HERTZ-SESSION bearer\" }\n                return \" + access token\"\n            }()\n            Self.log.debug(\"Trying MiniMax \\(prefix)cookies from \\(sourceLabel)\\(tokenLabel)\")\n            do {\n                let snapshot = try await MiniMaxUsageFetcher.fetchUsage(\n                    cookieHeader: cookieHeader,\n                    authorizationToken: token,\n                    groupID: groupID,\n                    region: fetchContext.region,\n                    environment: fetchContext.environment)\n                Self.log.debug(\"MiniMax \\(prefix)cookies valid from \\(sourceLabel)\")\n                return .success(snapshot)\n            } catch {\n                lastError = error\n                if Self.shouldTryNextBrowser(for: error) {\n                    continue\n                }\n                return .failure(error)\n            }\n        }\n\n        if let lastError {\n            return .failure(lastError)\n        }\n        return .failure(MiniMaxSettingsError.missingCookie)\n    }\n\n    private static func cookieValue(named name: String, in header: String) -> String? {\n        let parts = header.split(separator: \";\")\n        for part in parts {\n            let trimmed = part.trimmingCharacters(in: .whitespacesAndNewlines)\n            guard trimmed.lowercased().hasPrefix(\"\\(name.lowercased())=\") else { continue }\n            return String(trimmed.dropFirst(name.count + 1))\n        }\n        return nil\n    }\n\n    private static func shouldTryNextBrowser(for error: Error) -> Bool {\n        if case MiniMaxUsageError.invalidCredentials = error { return true }\n        if case MiniMaxUsageError.parseFailed = error { return true }\n        return false\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/MiniMax/MiniMaxSettingsReader.swift",
    "content": "import Foundation\n\npublic struct MiniMaxSettingsReader: Sendable {\n    public static let cookieHeaderKeys = [\n        \"MINIMAX_COOKIE\",\n        \"MINIMAX_COOKIE_HEADER\",\n    ]\n    public static let hostKey = \"MINIMAX_HOST\"\n    public static let codingPlanURLKey = \"MINIMAX_CODING_PLAN_URL\"\n    public static let remainsURLKey = \"MINIMAX_REMAINS_URL\"\n\n    public static func cookieHeader(\n        environment: [String: String] = ProcessInfo.processInfo.environment) -> String?\n    {\n        for key in self.cookieHeaderKeys {\n            guard let raw = environment[key]?.trimmingCharacters(in: .whitespacesAndNewlines),\n                  !raw.isEmpty\n            else {\n                continue\n            }\n            if MiniMaxCookieHeader.normalized(from: raw) != nil {\n                return raw\n            }\n        }\n        return nil\n    }\n\n    public static func hostOverride(environment: [String: String] = ProcessInfo.processInfo.environment) -> String? {\n        self.cleaned(environment[self.hostKey])\n    }\n\n    public static func codingPlanURL(\n        environment: [String: String] = ProcessInfo.processInfo.environment) -> URL?\n    {\n        self.url(from: environment[self.codingPlanURLKey])\n    }\n\n    public static func remainsURL(\n        environment: [String: String] = ProcessInfo.processInfo.environment) -> URL?\n    {\n        self.url(from: environment[self.remainsURLKey])\n    }\n\n    static func cleaned(_ raw: String?) -> String? {\n        guard var value = raw?.trimmingCharacters(in: .whitespacesAndNewlines), !value.isEmpty else {\n            return nil\n        }\n\n        if (value.hasPrefix(\"\\\"\") && value.hasSuffix(\"\\\"\")) ||\n            (value.hasPrefix(\"'\") && value.hasSuffix(\"'\"))\n        {\n            value.removeFirst()\n            value.removeLast()\n        }\n\n        value = value.trimmingCharacters(in: .whitespacesAndNewlines)\n        return value.isEmpty ? nil : value\n    }\n\n    private static func url(from raw: String?) -> URL? {\n        guard let cleaned = self.cleaned(raw) else { return nil }\n        if let url = URL(string: cleaned), url.scheme != nil {\n            return url\n        }\n        return URL(string: \"https://\\(cleaned)\")\n    }\n}\n\npublic enum MiniMaxSettingsError: LocalizedError, Sendable {\n    case missingCookie\n\n    public var errorDescription: String? {\n        switch self {\n        case .missingCookie:\n            \"MiniMax session not found. Sign in to platform.minimax.io or platform.minimaxi.com in your browser and try again.\"\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/MiniMax/MiniMaxUsageFetcher.swift",
    "content": "import Foundation\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n\npublic struct MiniMaxUsageFetcher: Sendable {\n    private static let log = CodexBarLog.logger(LogCategories.minimaxUsage)\n    private static let codingPlanPath = \"user-center/payment/coding-plan\"\n    private static let codingPlanQuery = \"cycle_type=3\"\n    private static let codingPlanRemainsPath = \"v1/api/openplatform/coding_plan/remains\"\n    private struct RemainsContext {\n        let authorizationToken: String?\n        let groupID: String?\n    }\n\n    public static func fetchUsage(\n        cookieHeader: String,\n        authorizationToken: String? = nil,\n        groupID: String? = nil,\n        region: MiniMaxAPIRegion = .global,\n        environment: [String: String] = ProcessInfo.processInfo.environment,\n        now: Date = Date()) async throws -> MiniMaxUsageSnapshot\n    {\n        guard let cookie = MiniMaxCookieHeader.normalized(from: cookieHeader) else {\n            throw MiniMaxUsageError.invalidCredentials\n        }\n\n        do {\n            return try await self.fetchCodingPlanHTML(\n                cookie: cookie,\n                authorizationToken: authorizationToken,\n                region: region,\n                environment: environment,\n                now: now)\n        } catch let error as MiniMaxUsageError {\n            if case .parseFailed = error {\n                Self.log.debug(\"MiniMax coding plan HTML parse failed, trying remains API\")\n                return try await self.fetchCodingPlanRemains(\n                    cookie: cookie,\n                    remainsContext: RemainsContext(\n                        authorizationToken: authorizationToken,\n                        groupID: groupID),\n                    region: region,\n                    environment: environment,\n                    now: now)\n            }\n            throw error\n        }\n    }\n\n    public static func fetchUsage(\n        apiToken: String,\n        region: MiniMaxAPIRegion = .global,\n        now: Date = Date()) async throws -> MiniMaxUsageSnapshot\n    {\n        let cleaned = apiToken.trimmingCharacters(in: .whitespacesAndNewlines)\n        guard !cleaned.isEmpty else {\n            throw MiniMaxUsageError.invalidCredentials\n        }\n\n        // Historically, MiniMax API token fetching used a China endpoint by default in some configurations. If the\n        // user has no persisted region and we default to `.global`, retry the China endpoint when the global host\n        // rejects the token so upgrades don't regress existing setups.\n        if region != .global {\n            return try await self.fetchUsageOnce(apiToken: cleaned, region: region, now: now)\n        }\n\n        do {\n            return try await self.fetchUsageOnce(apiToken: cleaned, region: .global, now: now)\n        } catch let error as MiniMaxUsageError {\n            guard case .invalidCredentials = error else { throw error }\n            Self.log.debug(\"MiniMax API token rejected for global host, retrying China mainland host\")\n            do {\n                return try await self.fetchUsageOnce(apiToken: cleaned, region: .chinaMainland, now: now)\n            } catch {\n                // Preserve the original invalid-credentials error so the fetch pipeline can fall back to web.\n                Self.log.debug(\"MiniMax China mainland retry failed, preserving global invalidCredentials\")\n                throw MiniMaxUsageError.invalidCredentials\n            }\n        }\n    }\n\n    private static func fetchUsageOnce(\n        apiToken: String,\n        region: MiniMaxAPIRegion,\n        now: Date) async throws -> MiniMaxUsageSnapshot\n    {\n        var request = URLRequest(url: region.apiRemainsURL)\n        request.httpMethod = \"GET\"\n        request.setValue(\"Bearer \\(apiToken)\", forHTTPHeaderField: \"Authorization\")\n        request.setValue(\"application/json\", forHTTPHeaderField: \"accept\")\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Content-Type\")\n        request.setValue(\"CodexBar\", forHTTPHeaderField: \"MM-API-Source\")\n\n        let (data, response) = try await URLSession.shared.data(for: request)\n        guard let httpResponse = response as? HTTPURLResponse else {\n            throw MiniMaxUsageError.networkError(\"Invalid response\")\n        }\n\n        guard httpResponse.statusCode == 200 else {\n            let body = String(data: data, encoding: .utf8) ?? \"\"\n            Self.log.error(\"MiniMax returned \\(httpResponse.statusCode): \\(body)\")\n            if httpResponse.statusCode == 401 || httpResponse.statusCode == 403 {\n                throw MiniMaxUsageError.invalidCredentials\n            }\n            throw MiniMaxUsageError.apiError(\"HTTP \\(httpResponse.statusCode)\")\n        }\n\n        return try MiniMaxUsageParser.parseCodingPlanRemains(data: data, now: now)\n    }\n\n    private static func fetchCodingPlanHTML(\n        cookie: String,\n        authorizationToken: String?,\n        region: MiniMaxAPIRegion,\n        environment: [String: String],\n        now: Date) async throws -> MiniMaxUsageSnapshot\n    {\n        let url = self.resolveCodingPlanURL(region: region, environment: environment)\n        var request = URLRequest(url: url)\n        request.httpMethod = \"GET\"\n        request.setValue(cookie, forHTTPHeaderField: \"Cookie\")\n        if let authorizationToken {\n            request.setValue(\"Bearer \\(authorizationToken)\", forHTTPHeaderField: \"Authorization\")\n        }\n        let acceptHeader = \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\"\n        request.setValue(acceptHeader, forHTTPHeaderField: \"accept\")\n        let userAgent =\n            \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) \" +\n            \"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36\"\n        request.setValue(userAgent, forHTTPHeaderField: \"user-agent\")\n        request.setValue(\"en-US,en;q=0.9\", forHTTPHeaderField: \"accept-language\")\n        let origin = self.originURL(from: url)\n        request.setValue(origin.absoluteString, forHTTPHeaderField: \"origin\")\n        request.setValue(\n            self.resolveCodingPlanRefererURL(region: region, environment: environment).absoluteString,\n            forHTTPHeaderField: \"referer\")\n\n        let (data, response) = try await URLSession.shared.data(for: request)\n        guard let httpResponse = response as? HTTPURLResponse else {\n            throw MiniMaxUsageError.networkError(\"Invalid response\")\n        }\n\n        guard httpResponse.statusCode == 200 else {\n            let body = String(data: data, encoding: .utf8) ?? \"\"\n            Self.log.error(\"MiniMax returned \\(httpResponse.statusCode): \\(body)\")\n            if httpResponse.statusCode == 401 || httpResponse.statusCode == 403 {\n                throw MiniMaxUsageError.invalidCredentials\n            }\n            throw MiniMaxUsageError.apiError(\"HTTP \\(httpResponse.statusCode)\")\n        }\n\n        if let contentType = httpResponse.value(forHTTPHeaderField: \"Content-Type\"),\n           contentType.lowercased().contains(\"application/json\")\n        {\n            return try MiniMaxUsageParser.parseCodingPlanRemains(data: data, now: now)\n        }\n\n        let html = String(data: data, encoding: .utf8) ?? \"\"\n        if html.contains(\"__NEXT_DATA__\") {\n            Self.log.debug(\"MiniMax coding plan HTML contains __NEXT_DATA__\")\n        }\n        if self.looksSignedOut(html: html) {\n            throw MiniMaxUsageError.invalidCredentials\n        }\n        return try MiniMaxUsageParser.parse(html: html, now: now)\n    }\n\n    private static func fetchCodingPlanRemains(\n        cookie: String,\n        remainsContext: RemainsContext,\n        region: MiniMaxAPIRegion,\n        environment: [String: String],\n        now: Date) async throws -> MiniMaxUsageSnapshot\n    {\n        let baseRemainsURL = self.resolveRemainsURL(region: region, environment: environment)\n        let remainsURL = self.appendGroupID(remainsContext.groupID, to: baseRemainsURL)\n        var request = URLRequest(url: remainsURL)\n        request.httpMethod = \"GET\"\n        request.setValue(cookie, forHTTPHeaderField: \"Cookie\")\n        if let authorizationToken = remainsContext.authorizationToken {\n            request.setValue(\"Bearer \\(authorizationToken)\", forHTTPHeaderField: \"Authorization\")\n        }\n        let acceptHeader = \"application/json, text/plain, */*\"\n        request.setValue(acceptHeader, forHTTPHeaderField: \"accept\")\n        request.setValue(\"XMLHttpRequest\", forHTTPHeaderField: \"x-requested-with\")\n        let userAgent =\n            \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) \" +\n            \"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36\"\n        request.setValue(userAgent, forHTTPHeaderField: \"user-agent\")\n        request.setValue(\"en-US,en;q=0.9\", forHTTPHeaderField: \"accept-language\")\n        let origin = self.originURL(from: baseRemainsURL)\n        request.setValue(origin.absoluteString, forHTTPHeaderField: \"origin\")\n        request.setValue(\n            self.resolveCodingPlanRefererURL(region: region, environment: environment).absoluteString,\n            forHTTPHeaderField: \"referer\")\n\n        let (data, response) = try await URLSession.shared.data(for: request)\n        guard let httpResponse = response as? HTTPURLResponse else {\n            throw MiniMaxUsageError.networkError(\"Invalid response\")\n        }\n\n        guard httpResponse.statusCode == 200 else {\n            let body = String(data: data, encoding: .utf8) ?? \"\"\n            Self.log.error(\"MiniMax returned \\(httpResponse.statusCode): \\(body)\")\n            if httpResponse.statusCode == 401 || httpResponse.statusCode == 403 {\n                throw MiniMaxUsageError.invalidCredentials\n            }\n            throw MiniMaxUsageError.apiError(\"HTTP \\(httpResponse.statusCode)\")\n        }\n\n        if let contentType = httpResponse.value(forHTTPHeaderField: \"Content-Type\"),\n           contentType.lowercased().contains(\"application/json\")\n        {\n            let payload = try MiniMaxUsageParser.decodePayload(data: data)\n            self.logCodingPlanStatus(payload: payload)\n            return try MiniMaxUsageParser.parseCodingPlanRemains(payload: payload, now: now)\n        }\n\n        let html = String(data: data, encoding: .utf8) ?? \"\"\n        if self.looksSignedOut(html: html) {\n            throw MiniMaxUsageError.invalidCredentials\n        }\n        return try MiniMaxUsageParser.parse(html: html, now: now)\n    }\n\n    private static func appendGroupID(_ groupID: String?, to url: URL) -> URL {\n        guard let groupID, !groupID.isEmpty else { return url }\n        guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return url }\n        var queryItems = components.queryItems ?? []\n        queryItems.append(URLQueryItem(name: \"GroupId\", value: groupID))\n        components.queryItems = queryItems\n        return components.url ?? url\n    }\n\n    static func originURL(from url: URL) -> URL {\n        guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return url }\n        components.path = \"\"\n        components.query = nil\n        components.fragment = nil\n        return components.url ?? url\n    }\n\n    static func resolveCodingPlanURL(\n        region: MiniMaxAPIRegion,\n        environment: [String: String]) -> URL\n    {\n        if let override = MiniMaxSettingsReader.codingPlanURL(environment: environment) {\n            return override\n        }\n        if let host = MiniMaxSettingsReader.hostOverride(environment: environment),\n           let hostURL = self.url(from: host, path: Self.codingPlanPath, query: Self.codingPlanQuery)\n        {\n            return hostURL\n        }\n        return region.codingPlanURL\n    }\n\n    static func resolveCodingPlanRefererURL(\n        region: MiniMaxAPIRegion,\n        environment: [String: String]) -> URL\n    {\n        if let override = MiniMaxSettingsReader.codingPlanURL(environment: environment) {\n            if var components = URLComponents(url: override, resolvingAgainstBaseURL: false) {\n                components.query = nil\n                return components.url ?? override\n            }\n            return override\n        }\n        if let host = MiniMaxSettingsReader.hostOverride(environment: environment),\n           let hostURL = self.url(from: host, path: Self.codingPlanPath)\n        {\n            return hostURL\n        }\n        return region.codingPlanRefererURL\n    }\n\n    static func resolveRemainsURL(\n        region: MiniMaxAPIRegion,\n        environment: [String: String]) -> URL\n    {\n        if let override = MiniMaxSettingsReader.remainsURL(environment: environment) {\n            return override\n        }\n        if let host = MiniMaxSettingsReader.hostOverride(environment: environment),\n           let hostURL = self.url(from: host, path: Self.codingPlanRemainsPath)\n        {\n            return hostURL\n        }\n        return region.remainsURL\n    }\n\n    static func url(from raw: String, path: String? = nil, query: String? = nil) -> URL? {\n        guard let cleaned = MiniMaxSettingsReader.cleaned(raw) else { return nil }\n\n        func compose(_ base: URL) -> URL? {\n            var components = URLComponents(url: base, resolvingAgainstBaseURL: false)!\n            if let path { components.path = \"/\" + path }\n            if let query { components.query = query }\n            return components.url\n        }\n\n        if let url = URL(string: cleaned), url.scheme != nil {\n            if let composed = compose(url) { return composed }\n            return url\n        }\n        guard let base = URL(string: \"https://\\(cleaned)\") else { return nil }\n        return compose(base)\n    }\n\n    private static func logCodingPlanStatus(payload: MiniMaxCodingPlanPayload) {\n        let baseResponse = payload.data.baseResp ?? payload.baseResp\n        guard let status = baseResponse?.statusCode else { return }\n        let message = baseResponse?.statusMessage ?? \"\"\n        if !message.isEmpty {\n            Self.log.debug(\"MiniMax coding plan status \\(status): \\(message)\")\n        } else {\n            Self.log.debug(\"MiniMax coding plan status \\(status)\")\n        }\n    }\n\n    private static func looksSignedOut(html: String) -> Bool {\n        let lower = html.lowercased()\n        return lower.contains(\"sign in\") || lower.contains(\"log in\") || lower.contains(\"登录\") || lower.contains(\"登入\")\n    }\n}\n\nstruct MiniMaxCodingPlanPayload: Decodable {\n    let baseResp: MiniMaxBaseResponse?\n    let data: MiniMaxCodingPlanData\n\n    private enum CodingKeys: String, CodingKey {\n        case baseResp = \"base_resp\"\n        case data\n    }\n\n    init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        self.baseResp = try container.decodeIfPresent(MiniMaxBaseResponse.self, forKey: .baseResp)\n        if container.contains(.data) {\n            let dataDecoder = try container.superDecoder(forKey: .data)\n            self.data = try MiniMaxCodingPlanData(from: dataDecoder)\n        } else {\n            self.data = try MiniMaxCodingPlanData(from: decoder)\n        }\n    }\n}\n\nstruct MiniMaxCodingPlanData: Decodable {\n    let baseResp: MiniMaxBaseResponse?\n    let currentSubscribeTitle: String?\n    let planName: String?\n    let comboTitle: String?\n    let currentPlanTitle: String?\n    let currentComboCard: MiniMaxComboCard?\n    let modelRemains: [MiniMaxModelRemains]\n\n    private enum CodingKeys: String, CodingKey {\n        case baseResp = \"base_resp\"\n        case currentSubscribeTitle = \"current_subscribe_title\"\n        case planName = \"plan_name\"\n        case comboTitle = \"combo_title\"\n        case currentPlanTitle = \"current_plan_title\"\n        case currentComboCard = \"current_combo_card\"\n        case modelRemains = \"model_remains\"\n    }\n\n    init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        self.baseResp = try container.decodeIfPresent(MiniMaxBaseResponse.self, forKey: .baseResp)\n        self.currentSubscribeTitle = try container.decodeIfPresent(String.self, forKey: .currentSubscribeTitle)\n        self.planName = try container.decodeIfPresent(String.self, forKey: .planName)\n        self.comboTitle = try container.decodeIfPresent(String.self, forKey: .comboTitle)\n        self.currentPlanTitle = try container.decodeIfPresent(String.self, forKey: .currentPlanTitle)\n        self.currentComboCard = try container.decodeIfPresent(MiniMaxComboCard.self, forKey: .currentComboCard)\n        self.modelRemains = try (container.decodeIfPresent([MiniMaxModelRemains].self, forKey: .modelRemains)) ?? []\n    }\n}\n\nstruct MiniMaxComboCard: Decodable {\n    let title: String?\n}\n\nstruct MiniMaxModelRemains: Decodable {\n    let currentIntervalTotalCount: Int?\n    let currentIntervalUsageCount: Int?\n    let startTime: Int?\n    let endTime: Int?\n    let remainsTime: Int?\n\n    private enum CodingKeys: String, CodingKey {\n        case currentIntervalTotalCount = \"current_interval_total_count\"\n        case currentIntervalUsageCount = \"current_interval_usage_count\"\n        case startTime = \"start_time\"\n        case endTime = \"end_time\"\n        case remainsTime = \"remains_time\"\n    }\n\n    init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        self.currentIntervalTotalCount = MiniMaxDecoding.decodeInt(container, forKey: .currentIntervalTotalCount)\n        self.currentIntervalUsageCount = MiniMaxDecoding.decodeInt(container, forKey: .currentIntervalUsageCount)\n        self.startTime = MiniMaxDecoding.decodeInt(container, forKey: .startTime)\n        self.endTime = MiniMaxDecoding.decodeInt(container, forKey: .endTime)\n        self.remainsTime = MiniMaxDecoding.decodeInt(container, forKey: .remainsTime)\n    }\n}\n\nstruct MiniMaxBaseResponse: Decodable {\n    let statusCode: Int?\n    let statusMessage: String?\n\n    private enum CodingKeys: String, CodingKey {\n        case statusCode = \"status_code\"\n        case statusMessage = \"status_msg\"\n    }\n\n    init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        self.statusCode = MiniMaxDecoding.decodeInt(container, forKey: .statusCode)\n        self.statusMessage = try container.decodeIfPresent(String.self, forKey: .statusMessage)\n    }\n}\n\nenum MiniMaxDecoding {\n    static func decodeInt<K: CodingKey>(_ container: KeyedDecodingContainer<K>, forKey key: K) -> Int? {\n        if let value = try? container.decodeIfPresent(Int.self, forKey: key) {\n            return value\n        }\n        if let value = try? container.decodeIfPresent(Int64.self, forKey: key) {\n            return Int(value)\n        }\n        if let value = try? container.decodeIfPresent(Double.self, forKey: key) {\n            return Int(value)\n        }\n        if let value = try? container.decodeIfPresent(String.self, forKey: key) {\n            let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)\n            return Int(trimmed)\n        }\n        return nil\n    }\n}\n\nenum MiniMaxUsageParser {\n    static func decodePayload(data: Data) throws -> MiniMaxCodingPlanPayload {\n        let decoder = JSONDecoder()\n        return try decoder.decode(MiniMaxCodingPlanPayload.self, from: data)\n    }\n\n    static func decodePayload(json: [String: Any]) throws -> MiniMaxCodingPlanPayload {\n        let normalized = self.normalizeCodingPlanPayload(json)\n        let data = try JSONSerialization.data(withJSONObject: normalized, options: [])\n        return try self.decodePayload(data: data)\n    }\n\n    static func parseCodingPlanRemains(data: Data, now: Date = Date()) throws -> MiniMaxUsageSnapshot {\n        let payload = try self.decodePayload(data: data)\n        return try self.parseCodingPlanRemains(payload: payload, now: now)\n    }\n\n    static func parse(html: String, now: Date = Date()) throws -> MiniMaxUsageSnapshot {\n        if let snapshot = self.parseNextData(html: html, now: now) {\n            return snapshot\n        }\n        let text = self.stripHTML(html)\n\n        let planName = self.parsePlanName(html: html, text: text)\n        let available = self.parseAvailableUsage(text: text)\n        let usedPercent = self.parseUsedPercent(text: text)\n        let resetsAt = self.parseResetsAt(text: text, now: now)\n\n        if planName == nil, available == nil, usedPercent == nil {\n            throw MiniMaxUsageError.parseFailed(\"Missing coding plan data.\")\n        }\n\n        return MiniMaxUsageSnapshot(\n            planName: planName,\n            availablePrompts: available?.prompts,\n            currentPrompts: nil,\n            remainingPrompts: nil,\n            windowMinutes: available?.windowMinutes,\n            usedPercent: usedPercent,\n            resetsAt: resetsAt,\n            updatedAt: now)\n    }\n\n    static func parseCodingPlanRemains(\n        payload: MiniMaxCodingPlanPayload,\n        now: Date = Date()) throws -> MiniMaxUsageSnapshot\n    {\n        let baseResponse = payload.data.baseResp ?? payload.baseResp\n        if let status = baseResponse?.statusCode, status != 0 {\n            let message = baseResponse?.statusMessage ?? \"status_code \\(status)\"\n            let lower = message.lowercased()\n            if status == 1004 || lower.contains(\"cookie\") || lower.contains(\"log in\") || lower.contains(\"login\") {\n                throw MiniMaxUsageError.invalidCredentials\n            }\n            throw MiniMaxUsageError.apiError(message)\n        }\n\n        guard let first = payload.data.modelRemains.first else {\n            throw MiniMaxUsageError.parseFailed(\"Missing coding plan data.\")\n        }\n\n        let total = first.currentIntervalTotalCount\n        let remaining = first.currentIntervalUsageCount\n        let usedPercent = self.usedPercent(total: total, remaining: remaining)\n\n        let windowMinutes = self.windowMinutes(\n            start: self.dateFromEpoch(first.startTime),\n            end: self.dateFromEpoch(first.endTime))\n\n        let resetsAt = self.resetsAt(\n            end: self.dateFromEpoch(first.endTime),\n            remains: first.remainsTime,\n            now: now)\n\n        let planName = self.parsePlanName(data: payload.data)\n\n        if planName == nil, total == nil, usedPercent == nil {\n            throw MiniMaxUsageError.parseFailed(\"Missing coding plan data.\")\n        }\n\n        let currentPrompts: Int? = if let total, let remaining {\n            max(0, total - remaining)\n        } else {\n            nil\n        }\n\n        return MiniMaxUsageSnapshot(\n            planName: planName,\n            availablePrompts: total,\n            currentPrompts: currentPrompts,\n            remainingPrompts: remaining,\n            windowMinutes: windowMinutes,\n            usedPercent: usedPercent,\n            resetsAt: resetsAt,\n            updatedAt: now)\n    }\n\n    private static func usedPercent(total: Int?, remaining: Int?) -> Double? {\n        guard let total, total > 0, let remaining else { return nil }\n        let used = max(0, total - remaining)\n        let percent = Double(used) / Double(total) * 100\n        return min(100, max(0, percent))\n    }\n\n    private static func dateFromEpoch(_ value: Int?) -> Date? {\n        guard let raw = value else { return nil }\n        if raw > 1_000_000_000_000 {\n            return Date(timeIntervalSince1970: TimeInterval(raw) / 1000)\n        }\n        if raw > 1_000_000_000 {\n            return Date(timeIntervalSince1970: TimeInterval(raw))\n        }\n        return nil\n    }\n\n    private static func windowMinutes(start: Date?, end: Date?) -> Int? {\n        guard let start, let end else { return nil }\n        let minutes = Int(end.timeIntervalSince(start) / 60)\n        return minutes > 0 ? minutes : nil\n    }\n\n    private static func resetsAt(end: Date?, remains: Int?, now: Date) -> Date? {\n        if let end, end > now {\n            return end\n        }\n        guard let remains, remains > 0 else { return nil }\n        let seconds: TimeInterval = remains > 1_000_000 ? TimeInterval(remains) / 1000 : TimeInterval(remains)\n        return now.addingTimeInterval(seconds)\n    }\n\n    private static func parsePlanName(data: MiniMaxCodingPlanData) -> String? {\n        let candidates = [\n            data.currentSubscribeTitle,\n            data.planName,\n            data.comboTitle,\n            data.currentPlanTitle,\n            data.currentComboCard?.title,\n        ].compactMap(\\.self)\n\n        for candidate in candidates {\n            let trimmed = candidate.trimmingCharacters(in: .whitespacesAndNewlines)\n            if !trimmed.isEmpty { return trimmed }\n        }\n        return nil\n    }\n\n    private static func parsePlanName(html: String, text: String) -> String? {\n        let candidates = [\n            self.extractFirst(pattern: #\"(?i)\"planName\"\\s*:\\s*\"([^\"]+)\"\"#, text: html),\n            self.extractFirst(pattern: #\"(?i)\"plan\"\\s*:\\s*\"([^\"]+)\"\"#, text: html),\n            self.extractFirst(pattern: #\"(?i)\"packageName\"\\s*:\\s*\"([^\"]+)\"\"#, text: html),\n            self.extractFirst(pattern: #\"(?i)Coding\\s*Plan\\s*([A-Za-z0-9][A-Za-z0-9\\s._-]{0,32})\"#, text: text),\n        ].compactMap(\\.self)\n\n        for candidate in candidates {\n            let cleaned = UsageFormatter.cleanPlanName(candidate)\n            let trimmed = cleaned\n                .replacingOccurrences(\n                    of: #\"(?i)\\s+available\\s+usage.*$\"#,\n                    with: \"\",\n                    options: .regularExpression)\n                .trimmingCharacters(in: .whitespacesAndNewlines)\n            if !trimmed.isEmpty { return trimmed }\n        }\n        return nil\n    }\n\n    private static func parseNextData(html: String, now: Date) -> MiniMaxUsageSnapshot? {\n        guard let data = self.nextDataJSONData(fromHTML: html),\n              let object = try? JSONSerialization.jsonObject(with: data, options: []),\n              let payload = self.findCodingPlanPayload(in: object),\n              let decoded = try? self.decodePayload(json: payload)\n        else {\n            return nil\n        }\n        return try? self.parseCodingPlanRemains(payload: decoded, now: now)\n    }\n\n    private static func findCodingPlanPayload(in object: Any) -> [String: Any]? {\n        if let dict = object as? [String: Any] {\n            if dict[\"model_remains\"] != nil || dict[\"modelRemains\"] != nil {\n                return dict\n            }\n            for value in dict.values {\n                if let match = self.findCodingPlanPayload(in: value) {\n                    return match\n                }\n            }\n            return nil\n        }\n\n        if let array = object as? [Any] {\n            for value in array {\n                if let match = self.findCodingPlanPayload(in: value) {\n                    return match\n                }\n            }\n        }\n        return nil\n    }\n\n    private static func normalizeCodingPlanPayload(_ payload: [String: Any]) -> [String: Any] {\n        var normalized = payload\n\n        if normalized[\"model_remains\"] == nil, let value = normalized[\"modelRemains\"] {\n            normalized[\"model_remains\"] = value\n        }\n        if normalized[\"current_subscribe_title\"] == nil, let value = normalized[\"currentSubscribeTitle\"] {\n            normalized[\"current_subscribe_title\"] = value\n        }\n        if normalized[\"plan_name\"] == nil, let value = normalized[\"planName\"] {\n            normalized[\"plan_name\"] = value\n        }\n        if normalized[\"combo_title\"] == nil, let value = normalized[\"comboTitle\"] {\n            normalized[\"combo_title\"] = value\n        }\n        if normalized[\"current_plan_title\"] == nil, let value = normalized[\"currentPlanTitle\"] {\n            normalized[\"current_plan_title\"] = value\n        }\n        if normalized[\"current_combo_card\"] == nil, let value = normalized[\"currentComboCard\"] {\n            normalized[\"current_combo_card\"] = value\n        }\n        if normalized[\"base_resp\"] == nil, let value = normalized[\"baseResp\"] {\n            normalized[\"base_resp\"] = value\n        }\n\n        if let data = normalized[\"data\"] as? [String: Any] {\n            normalized[\"data\"] = self.normalizeCodingPlanPayload(data)\n        }\n\n        return normalized\n    }\n\n    private static let nextDataNeedle = Data(\"id=\\\"__NEXT_DATA__\\\"\".utf8)\n    private static let scriptCloseNeedle = Data(\"</script>\".utf8)\n\n    private static func nextDataJSONData(fromHTML html: String) -> Data? {\n        let data = Data(html.utf8)\n        guard let idRange = data.range(of: self.nextDataNeedle) else { return nil }\n        guard let openTagEnd = data[idRange.upperBound...].firstIndex(of: UInt8(ascii: \">\")) else { return nil }\n        let contentStart = data.index(after: openTagEnd)\n        guard let closeRange = data.range(\n            of: self.scriptCloseNeedle,\n            options: [],\n            in: contentStart..<data.endIndex)\n        else { return nil }\n        let rawData = data[contentStart..<closeRange.lowerBound]\n        let trimmed = self.trimASCIIWhitespace(Data(rawData))\n        return trimmed.isEmpty ? nil : trimmed\n    }\n\n    private static func trimASCIIWhitespace(_ data: Data) -> Data {\n        guard !data.isEmpty else { return data }\n        var start = data.startIndex\n        var end = data.endIndex\n\n        while start < end, self.isASCIIWhitespace(data[start]) {\n            start = data.index(after: start)\n        }\n        while end > start {\n            let prev = data.index(before: end)\n            if self.isASCIIWhitespace(data[prev]) {\n                end = prev\n            } else {\n                break\n            }\n        }\n        return data.subdata(in: start..<end)\n    }\n\n    private static func isASCIIWhitespace(_ value: UInt8) -> Bool {\n        switch value {\n        case 9, 10, 13, 32:\n            true\n        default:\n            false\n        }\n    }\n\n    private static func parseAvailableUsage(text: String) -> (prompts: Int, windowMinutes: Int)? {\n        let pattern =\n            #\"(?i)available\\s+usage[:\\s]*([0-9][0-9,]*)\\s*prompts?\\s*/\\s*\"# +\n            #\"([0-9]+(?:\\.[0-9]+)?)\\s*(hours?|hrs?|h|minutes?|mins?|m|days?|d)\"#\n        guard let match = self.extractMatch(pattern: pattern, text: text), match.count >= 3 else { return nil }\n        let promptsRaw = match[0]\n        let durationRaw = match[1]\n        let unitRaw = match[2]\n\n        let prompts = Int(promptsRaw.replacingOccurrences(of: \",\", with: \"\")) ?? 0\n        guard prompts > 0 else { return nil }\n\n        guard let duration = Double(durationRaw) else { return nil }\n        let windowMinutes = self.minutes(from: duration, unit: unitRaw)\n        guard windowMinutes > 0 else { return nil }\n        return (prompts, windowMinutes)\n    }\n\n    private static func parseUsedPercent(text: String) -> Double? {\n        let patterns = [\n            #\"(?i)([0-9]{1,3}(?:\\.[0-9]+)?)\\s*%\\s*used\"#,\n            #\"(?i)used\\s*([0-9]{1,3}(?:\\.[0-9]+)?)\\s*%\"#,\n        ]\n        for pattern in patterns {\n            if let raw = self.extractFirst(pattern: pattern, text: text),\n               let value = Double(raw),\n               value >= 0,\n               value <= 100\n            {\n                return value\n            }\n        }\n        return nil\n    }\n\n    private static func parseResetsAt(text: String, now: Date) -> Date? {\n        if let match = self.extractMatch(\n            pattern: #\"(?i)resets?\\s+in\\s+([0-9]+)\\s*(seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d)\"#,\n            text: text),\n            match.count >= 2,\n            let value = Double(match[0])\n        {\n            let unit = match[1]\n            let seconds = self.seconds(from: value, unit: unit)\n            return now.addingTimeInterval(seconds)\n        }\n\n        if let match = self.extractMatch(\n            pattern: #\"(?i)resets?\\s+at\\s+([0-9]{1,2}:[0-9]{2})(?:\\s*\\(([^)]+)\\))?\"#,\n            text: text),\n            match.count >= 1\n        {\n            let timeText = match[0]\n            let tzText = match.count > 1 ? match[1] : nil\n            return self.dateForTime(timeText, timeZoneHint: tzText, now: now)\n        }\n\n        return nil\n    }\n\n    private static func dateForTime(_ time: String, timeZoneHint: String?, now: Date) -> Date? {\n        let formatter = DateFormatter()\n        formatter.dateFormat = \"HH:mm\"\n        if let tzHint = timeZoneHint?.trimmingCharacters(in: .whitespacesAndNewlines),\n           !tzHint.isEmpty\n        {\n            formatter.timeZone = TimeZone(identifier: tzHint)\n        }\n        formatter.locale = Locale(identifier: \"en_US_POSIX\")\n\n        guard let timeOnly = formatter.date(from: time) else { return nil }\n        var calendar = Calendar(identifier: .gregorian)\n        calendar.timeZone = formatter.timeZone ?? .current\n\n        let nowComponents = calendar.dateComponents([.year, .month, .day], from: now)\n        var targetComponents = calendar.dateComponents([.hour, .minute], from: timeOnly)\n        targetComponents.year = nowComponents.year\n        targetComponents.month = nowComponents.month\n        targetComponents.day = nowComponents.day\n        guard var candidate = calendar.date(from: targetComponents) else { return nil }\n\n        if candidate < now {\n            candidate = calendar.date(byAdding: .day, value: 1, to: candidate) ?? candidate\n        }\n        return candidate\n    }\n\n    private static func minutes(from value: Double, unit: String) -> Int {\n        let lower = unit.lowercased()\n        if lower.hasPrefix(\"d\") { return Int((value * 24 * 60).rounded()) }\n        if lower.hasPrefix(\"h\") { return Int((value * 60).rounded()) }\n        if lower.hasPrefix(\"m\") { return Int(value.rounded()) }\n        if lower.hasPrefix(\"s\") { return max(1, Int((value / 60).rounded())) }\n        return 0\n    }\n\n    private static func seconds(from value: Double, unit: String) -> TimeInterval {\n        let lower = unit.lowercased()\n        if lower.hasPrefix(\"d\") { return value * 24 * 60 * 60 }\n        if lower.hasPrefix(\"h\") { return value * 60 * 60 }\n        if lower.hasPrefix(\"m\") { return value * 60 }\n        return value\n    }\n\n    private static func stripHTML(_ html: String) -> String {\n        var text = html.replacingOccurrences(of: \"<[^>]+>\", with: \" \", options: .regularExpression)\n        text = text.replacingOccurrences(of: \"&nbsp;\", with: \" \")\n        text = text.replacingOccurrences(of: \"&amp;\", with: \"&\")\n        text = text.replacingOccurrences(of: \"&lt;\", with: \"<\")\n        text = text.replacingOccurrences(of: \"&gt;\", with: \">\")\n        text = text.replacingOccurrences(of: #\"\\s+\"#, with: \" \", options: .regularExpression)\n        return text.trimmingCharacters(in: .whitespacesAndNewlines)\n    }\n\n    private static func extractFirst(pattern: String, text: String) -> String? {\n        guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { return nil }\n        let range = NSRange(text.startIndex..<text.endIndex, in: text)\n        guard let match = regex.firstMatch(in: text, options: [], range: range),\n              match.numberOfRanges >= 2,\n              let captureRange = Range(match.range(at: 1), in: text)\n        else { return nil }\n        return String(text[captureRange]).trimmingCharacters(in: .whitespacesAndNewlines)\n    }\n\n    private static func extractMatch(pattern: String, text: String) -> [String]? {\n        guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { return nil }\n        let range = NSRange(text.startIndex..<text.endIndex, in: text)\n        guard let match = regex.firstMatch(in: text, options: [], range: range),\n              match.numberOfRanges >= 2\n        else { return nil }\n        return (1..<match.numberOfRanges).compactMap { idx in\n            guard let captureRange = Range(match.range(at: idx), in: text) else { return nil }\n            return String(text[captureRange]).trimmingCharacters(in: .whitespacesAndNewlines)\n        }\n    }\n}\n\npublic enum MiniMaxUsageError: LocalizedError, Sendable, Equatable {\n    case invalidCredentials\n    case networkError(String)\n    case apiError(String)\n    case parseFailed(String)\n\n    public var errorDescription: String? {\n        switch self {\n        case .invalidCredentials:\n            \"MiniMax credentials are invalid or expired.\"\n        case let .networkError(message):\n            \"MiniMax network error: \\(message)\"\n        case let .apiError(message):\n            \"MiniMax API error: \\(message)\"\n        case let .parseFailed(message):\n            \"Failed to parse MiniMax coding plan: \\(message)\"\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/MiniMax/MiniMaxUsageSnapshot.swift",
    "content": "import Foundation\n\npublic struct MiniMaxUsageSnapshot: Sendable {\n    public let planName: String?\n    public let availablePrompts: Int?\n    public let currentPrompts: Int?\n    public let remainingPrompts: Int?\n    public let windowMinutes: Int?\n    public let usedPercent: Double?\n    public let resetsAt: Date?\n    public let updatedAt: Date\n\n    public init(\n        planName: String?,\n        availablePrompts: Int?,\n        currentPrompts: Int?,\n        remainingPrompts: Int?,\n        windowMinutes: Int?,\n        usedPercent: Double?,\n        resetsAt: Date?,\n        updatedAt: Date)\n    {\n        self.planName = planName\n        self.availablePrompts = availablePrompts\n        self.currentPrompts = currentPrompts\n        self.remainingPrompts = remainingPrompts\n        self.windowMinutes = windowMinutes\n        self.usedPercent = usedPercent\n        self.resetsAt = resetsAt\n        self.updatedAt = updatedAt\n    }\n}\n\nextension MiniMaxUsageSnapshot {\n    public func toUsageSnapshot() -> UsageSnapshot {\n        let used = max(0, min(100, self.usedPercent ?? 0))\n        let resetDescription = self.limitDescription()\n        let primary = RateWindow(\n            usedPercent: used,\n            windowMinutes: self.windowMinutes,\n            resetsAt: self.resetsAt,\n            resetDescription: resetDescription)\n\n        let planName = self.planName?.trimmingCharacters(in: .whitespacesAndNewlines)\n        let loginMethod = (planName?.isEmpty ?? true) ? nil : planName\n        let identity = ProviderIdentitySnapshot(\n            providerID: .minimax,\n            accountEmail: nil,\n            accountOrganization: nil,\n            loginMethod: loginMethod)\n\n        return UsageSnapshot(\n            primary: primary,\n            secondary: nil,\n            tertiary: nil,\n            providerCost: nil,\n            minimaxUsage: self,\n            updatedAt: self.updatedAt,\n            identity: identity)\n    }\n\n    private func limitDescription() -> String? {\n        guard let availablePrompts, availablePrompts > 0 else {\n            return self.windowDescription()\n        }\n\n        if let windowDescription = self.windowDescription() {\n            return \"\\(availablePrompts) prompts / \\(windowDescription)\"\n        }\n        return \"\\(availablePrompts) prompts\"\n    }\n\n    private func windowDescription() -> String? {\n        guard let windowMinutes, windowMinutes > 0 else { return nil }\n        if windowMinutes % (24 * 60) == 0 {\n            let days = windowMinutes / (24 * 60)\n            return \"\\(days) \\(days == 1 ? \"day\" : \"days\")\"\n        }\n        if windowMinutes % 60 == 0 {\n            let hours = windowMinutes / 60\n            return \"\\(hours) \\(hours == 1 ? \"hour\" : \"hours\")\"\n        }\n        return \"\\(windowMinutes) \\(windowMinutes == 1 ? \"minute\" : \"minutes\")\"\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Ollama/OllamaProviderDescriptor.swift",
    "content": "import CodexBarMacroSupport\nimport Foundation\n\n@ProviderDescriptorRegistration\n@ProviderDescriptorDefinition\npublic enum OllamaProviderDescriptor {\n    static func makeDescriptor() -> ProviderDescriptor {\n        ProviderDescriptor(\n            id: .ollama,\n            metadata: ProviderMetadata(\n                id: .ollama,\n                displayName: \"Ollama\",\n                sessionLabel: \"Session\",\n                weeklyLabel: \"Weekly\",\n                opusLabel: nil,\n                supportsOpus: false,\n                supportsCredits: false,\n                creditsHint: \"\",\n                toggleTitle: \"Show Ollama usage\",\n                cliName: \"ollama\",\n                defaultEnabled: false,\n                isPrimaryProvider: false,\n                usesAccountFallback: false,\n                browserCookieOrder: ProviderBrowserCookieDefaults.defaultImportOrder,\n                dashboardURL: \"https://ollama.com/settings\",\n                statusPageURL: nil),\n            branding: ProviderBranding(\n                iconStyle: .ollama,\n                iconResourceName: \"ProviderIcon-ollama\",\n                color: ProviderColor(red: 136 / 255, green: 136 / 255, blue: 136 / 255)),\n            tokenCost: ProviderTokenCostConfig(\n                supportsTokenCost: false,\n                noDataMessage: { \"Ollama cost summary is not supported.\" }),\n            fetchPlan: ProviderFetchPlan(\n                sourceModes: [.auto, .web],\n                pipeline: ProviderFetchPipeline(resolveStrategies: { _ in [OllamaStatusFetchStrategy()] })),\n            cli: ProviderCLIConfig(\n                name: \"ollama\",\n                versionDetector: nil))\n    }\n}\n\nstruct OllamaStatusFetchStrategy: ProviderFetchStrategy {\n    let id: String = \"ollama.web\"\n    let kind: ProviderFetchKind = .web\n\n    func isAvailable(_ context: ProviderFetchContext) async -> Bool {\n        guard context.settings?.ollama?.cookieSource != .off else { return false }\n        return true\n    }\n\n    func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {\n        let fetcher = OllamaUsageFetcher(browserDetection: context.browserDetection)\n        let manual = Self.manualCookieHeader(from: context)\n        let isManualMode = context.settings?.ollama?.cookieSource == .manual\n        let logger: ((String) -> Void)? = context.verbose\n            ? { msg in CodexBarLog.logger(LogCategories.ollama).verbose(msg) }\n            : nil\n        let snap = try await fetcher.fetch(\n            cookieHeaderOverride: manual,\n            manualCookieMode: isManualMode,\n            logger: logger)\n        return self.makeResult(\n            usage: snap.toUsageSnapshot(),\n            sourceLabel: \"web\")\n    }\n\n    func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool {\n        false\n    }\n\n    private static func manualCookieHeader(from context: ProviderFetchContext) -> String? {\n        guard context.settings?.ollama?.cookieSource == .manual else { return nil }\n        return CookieHeaderNormalizer.normalize(context.settings?.ollama?.manualCookieHeader)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Ollama/OllamaUsageFetcher.swift",
    "content": "import Foundation\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n\n#if os(macOS)\nimport SweetCookieKit\n#endif\n\nprivate let ollamaSessionCookieNames: Set<String> = [\n    \"session\",\n    \"ollama_session\",\n    \"__Host-ollama_session\",\n    \"__Secure-next-auth.session-token\",\n    \"next-auth.session-token\",\n]\n\nprivate func isRecognizedOllamaSessionCookieName(_ name: String) -> Bool {\n    if ollamaSessionCookieNames.contains(name) { return true }\n    // next-auth can split tokens into chunked cookies: `<name>.0`, `<name>.1`, ...\n    return name.hasPrefix(\"__Secure-next-auth.session-token.\") ||\n        name.hasPrefix(\"next-auth.session-token.\")\n}\n\nprivate func hasRecognizedOllamaSessionCookie(in header: String) -> Bool {\n    CookieHeaderNormalizer.pairs(from: header).contains { pair in\n        isRecognizedOllamaSessionCookieName(pair.name)\n    }\n}\n\npublic enum OllamaUsageError: LocalizedError, Sendable {\n    case notLoggedIn\n    case invalidCredentials\n    case parseFailed(String)\n    case networkError(String)\n    case noSessionCookie\n\n    public var errorDescription: String? {\n        switch self {\n        case .notLoggedIn:\n            \"Not logged in to Ollama. Please log in via ollama.com/settings.\"\n        case .invalidCredentials:\n            \"Ollama session cookie expired. Please log in again.\"\n        case let .parseFailed(message):\n            \"Could not parse Ollama usage: \\(message)\"\n        case let .networkError(message):\n            \"Ollama request failed: \\(message)\"\n        case .noSessionCookie:\n            \"No Ollama session cookie found. Please log in to ollama.com in your browser.\"\n        }\n    }\n}\n\n#if os(macOS)\nprivate let ollamaCookieImportOrder: BrowserCookieImportOrder =\n    ProviderDefaults.metadata[.ollama]?.browserCookieOrder ?? Browser.defaultImportOrder\n\npublic enum OllamaCookieImporter {\n    private static let cookieClient = BrowserCookieClient()\n    private static let cookieDomains = [\"ollama.com\", \"www.ollama.com\"]\n    static let defaultPreferredBrowsers: [Browser] = [.chrome]\n\n    public struct SessionInfo: Sendable {\n        public let cookies: [HTTPCookie]\n        public let sourceLabel: String\n\n        public init(cookies: [HTTPCookie], sourceLabel: String) {\n            self.cookies = cookies\n            self.sourceLabel = sourceLabel\n        }\n\n        public var cookieHeader: String {\n            self.cookies.map { \"\\($0.name)=\\($0.value)\" }.joined(separator: \"; \")\n        }\n    }\n\n    public static func importSessions(\n        browserDetection: BrowserDetection,\n        preferredBrowsers: [Browser] = [.chrome],\n        allowFallbackBrowsers: Bool = false,\n        logger: ((String) -> Void)? = nil) throws -> [SessionInfo]\n    {\n        let log: (String) -> Void = { msg in logger?(\"[ollama-cookie] \\(msg)\") }\n        let preferredSources = preferredBrowsers.isEmpty\n            ? ollamaCookieImportOrder.cookieImportCandidates(using: browserDetection)\n            : preferredBrowsers.cookieImportCandidates(using: browserDetection)\n        let preferredCandidates = self.collectSessionInfo(from: preferredSources, logger: log)\n        return try self.selectSessionInfosWithFallback(\n            preferredCandidates: preferredCandidates,\n            allowFallbackBrowsers: allowFallbackBrowsers,\n            loadFallbackCandidates: {\n                guard !preferredBrowsers.isEmpty else { return [] }\n                let fallbackSources = self.fallbackBrowserSources(\n                    browserDetection: browserDetection,\n                    excluding: preferredSources)\n                guard !fallbackSources.isEmpty else { return [] }\n                log(\"No recognized Ollama session in preferred browsers; trying fallback import order\")\n                return self.collectSessionInfo(from: fallbackSources, logger: log)\n            },\n            logger: log)\n    }\n\n    public static func importSession(\n        browserDetection: BrowserDetection,\n        preferredBrowsers: [Browser] = [.chrome],\n        allowFallbackBrowsers: Bool = false,\n        logger: ((String) -> Void)? = nil) throws -> SessionInfo\n    {\n        let sessions = try self.importSessions(\n            browserDetection: browserDetection,\n            preferredBrowsers: preferredBrowsers,\n            allowFallbackBrowsers: allowFallbackBrowsers,\n            logger: logger)\n        guard let first = sessions.first else {\n            throw OllamaUsageError.noSessionCookie\n        }\n        return first\n    }\n\n    static func selectSessionInfos(\n        from candidates: [SessionInfo],\n        logger: ((String) -> Void)? = nil) throws -> [SessionInfo]\n    {\n        var recognized: [SessionInfo] = []\n        for candidate in candidates {\n            let names = candidate.cookies.map(\\.name).joined(separator: \", \")\n            logger?(\"\\(candidate.sourceLabel) cookies: \\(names)\")\n            if self.containsRecognizedSessionCookie(in: candidate.cookies) {\n                logger?(\"Found Ollama session cookie in \\(candidate.sourceLabel)\")\n                recognized.append(candidate)\n            } else {\n                logger?(\"\\(candidate.sourceLabel) cookies found, but no recognized session cookie present\")\n            }\n        }\n        guard !recognized.isEmpty else {\n            throw OllamaUsageError.noSessionCookie\n        }\n        return recognized\n    }\n\n    static func selectSessionInfo(\n        from candidates: [SessionInfo],\n        logger: ((String) -> Void)? = nil) throws -> SessionInfo\n    {\n        guard let first = try self.selectSessionInfos(from: candidates, logger: logger).first else {\n            throw OllamaUsageError.noSessionCookie\n        }\n        return first\n    }\n\n    static func selectSessionInfosWithFallback(\n        preferredCandidates: [SessionInfo],\n        allowFallbackBrowsers: Bool,\n        loadFallbackCandidates: () -> [SessionInfo],\n        logger: ((String) -> Void)? = nil) throws -> [SessionInfo]\n    {\n        guard allowFallbackBrowsers else {\n            return try self.selectSessionInfos(from: preferredCandidates, logger: logger)\n        }\n        do {\n            return try self.selectSessionInfos(from: preferredCandidates, logger: logger)\n        } catch OllamaUsageError.noSessionCookie {\n            let fallbackCandidates = loadFallbackCandidates()\n            return try self.selectSessionInfos(from: fallbackCandidates, logger: logger)\n        }\n    }\n\n    static func selectSessionInfoWithFallback(\n        preferredCandidates: [SessionInfo],\n        allowFallbackBrowsers: Bool,\n        loadFallbackCandidates: () -> [SessionInfo],\n        logger: ((String) -> Void)? = nil) throws -> SessionInfo\n    {\n        guard let first = try self.selectSessionInfosWithFallback(\n            preferredCandidates: preferredCandidates,\n            allowFallbackBrowsers: allowFallbackBrowsers,\n            loadFallbackCandidates: loadFallbackCandidates,\n            logger: logger).first\n        else {\n            throw OllamaUsageError.noSessionCookie\n        }\n        return first\n    }\n\n    private static func fallbackBrowserSources(\n        browserDetection: BrowserDetection,\n        excluding triedSources: [Browser]) -> [Browser]\n    {\n        let tried = Set(triedSources)\n        return ollamaCookieImportOrder.cookieImportCandidates(using: browserDetection)\n            .filter { !tried.contains($0) }\n    }\n\n    private static func collectSessionInfo(\n        from browserSources: [Browser],\n        logger: @escaping (String) -> Void) -> [SessionInfo]\n    {\n        var candidates: [SessionInfo] = []\n        for browserSource in browserSources {\n            do {\n                let query = BrowserCookieQuery(domains: self.cookieDomains)\n                let sources = try Self.cookieClient.records(\n                    matching: query,\n                    in: browserSource,\n                    logger: logger)\n                for source in sources where !source.records.isEmpty {\n                    let cookies = BrowserCookieClient.makeHTTPCookies(source.records, origin: query.origin)\n                    guard !cookies.isEmpty else { continue }\n                    candidates.append(SessionInfo(cookies: cookies, sourceLabel: source.label))\n                }\n            } catch {\n                BrowserCookieAccessGate.recordIfNeeded(error)\n                logger(\"\\(browserSource.displayName) cookie import failed: \\(error.localizedDescription)\")\n            }\n        }\n        return candidates\n    }\n\n    private static func containsRecognizedSessionCookie(in cookies: [HTTPCookie]) -> Bool {\n        cookies.contains { cookie in\n            isRecognizedOllamaSessionCookieName(cookie.name)\n        }\n    }\n}\n#endif\n\npublic struct OllamaUsageFetcher: Sendable {\n    private static let settingsURL = URL(string: \"https://ollama.com/settings\")!\n    @MainActor private static var recentDumps: [String] = []\n\n    private struct CookieCandidate {\n        let cookieHeader: String\n        let sourceLabel: String\n    }\n\n    enum RetryableParseFailure: Error {\n        case missingUsageData\n    }\n\n    public let browserDetection: BrowserDetection\n    private let makeURLSession: @Sendable (URLSessionTaskDelegate?) -> URLSession\n\n    public init(browserDetection: BrowserDetection) {\n        self.browserDetection = browserDetection\n        self.makeURLSession = { delegate in\n            URLSession(configuration: .ephemeral, delegate: delegate, delegateQueue: nil)\n        }\n    }\n\n    init(\n        browserDetection: BrowserDetection,\n        makeURLSession: @escaping @Sendable (URLSessionTaskDelegate?) -> URLSession)\n    {\n        self.browserDetection = browserDetection\n        self.makeURLSession = makeURLSession\n    }\n\n    public func fetch(\n        cookieHeaderOverride: String? = nil,\n        manualCookieMode: Bool = false,\n        logger: ((String) -> Void)? = nil,\n        now: Date = Date()) async throws -> OllamaUsageSnapshot\n    {\n        let cookieCandidates = try await self.resolveCookieCandidates(\n            override: cookieHeaderOverride,\n            manualCookieMode: manualCookieMode,\n            logger: logger)\n        return try await self.fetchUsingCookieCandidates(\n            cookieCandidates,\n            logger: logger,\n            now: now)\n    }\n\n    static func shouldRetryWithNextCookieCandidate(after error: Error) -> Bool {\n        switch error {\n        case OllamaUsageError.invalidCredentials, OllamaUsageError.notLoggedIn:\n            true\n        case RetryableParseFailure.missingUsageData:\n            true\n        default:\n            false\n        }\n    }\n\n    private func fetchUsingCookieCandidates(\n        _ candidates: [CookieCandidate],\n        logger: ((String) -> Void)?,\n        now: Date) async throws -> OllamaUsageSnapshot\n    {\n        do {\n            return try await ProviderCandidateRetryRunner.run(\n                candidates,\n                shouldRetry: { error in\n                    Self.shouldRetryWithNextCookieCandidate(after: error)\n                },\n                onRetry: { candidate, _ in\n                    logger?(\"[ollama] Auth failed for \\(candidate.sourceLabel); trying next cookie candidate\")\n                },\n                attempt: { candidate in\n                    logger?(\"[ollama] Using cookies from \\(candidate.sourceLabel)\")\n                    let names = self.cookieNames(from: candidate.cookieHeader)\n                    if !names.isEmpty {\n                        logger?(\"[ollama] Cookie names: \\(names.joined(separator: \", \"))\")\n                    }\n\n                    let diagnostics = RedirectDiagnostics(cookieHeader: candidate.cookieHeader, logger: logger)\n                    do {\n                        let (html, responseInfo) = try await self.fetchHTMLWithDiagnostics(\n                            cookieHeader: candidate.cookieHeader,\n                            diagnostics: diagnostics)\n                        if let logger {\n                            self.logDiagnostics(responseInfo: responseInfo, diagnostics: diagnostics, logger: logger)\n                        }\n                        do {\n                            return try Self.parseSnapshotForRetry(html: html, now: now)\n                        } catch {\n                            let surfacedError = Self.surfacedError(from: error)\n                            if let logger {\n                                logger(\"[ollama] Parse failed: \\(surfacedError.localizedDescription)\")\n                                self.logHTMLHints(html: html, logger: logger)\n                            }\n                            throw error\n                        }\n                    } catch {\n                        if let logger {\n                            self.logDiagnostics(responseInfo: nil, diagnostics: diagnostics, logger: logger)\n                        }\n                        throw error\n                    }\n                })\n        } catch ProviderCandidateRetryRunnerError.noCandidates {\n            throw OllamaUsageError.noSessionCookie\n        } catch {\n            throw Self.surfacedError(from: error)\n        }\n    }\n\n    private static func parseSnapshotForRetry(html: String, now: Date) throws -> OllamaUsageSnapshot {\n        switch OllamaUsageParser.parseClassified(html: html, now: now) {\n        case let .success(snapshot):\n            return snapshot\n        case .failure(.notLoggedIn):\n            throw OllamaUsageError.notLoggedIn\n        case .failure(.missingUsageData):\n            throw RetryableParseFailure.missingUsageData\n        }\n    }\n\n    private static func surfacedError(from error: Error) -> Error {\n        switch error {\n        case RetryableParseFailure.missingUsageData:\n            OllamaUsageError.parseFailed(\"Missing Ollama usage data.\")\n        default:\n            error\n        }\n    }\n\n    private func resolveCookieCandidates(\n        override: String?,\n        manualCookieMode: Bool,\n        logger: ((String) -> Void)?) async throws -> [CookieCandidate]\n    {\n        if let manualHeader = try Self.resolveManualCookieHeader(\n            override: override,\n            manualCookieMode: manualCookieMode,\n            logger: logger)\n        {\n            return [CookieCandidate(cookieHeader: manualHeader, sourceLabel: \"manual cookie header\")]\n        }\n        #if os(macOS)\n        let sessions = try OllamaCookieImporter.importSessions(browserDetection: self.browserDetection, logger: logger)\n        return sessions.map { session in\n            CookieCandidate(cookieHeader: session.cookieHeader, sourceLabel: session.sourceLabel)\n        }\n        #else\n        throw OllamaUsageError.noSessionCookie\n        #endif\n    }\n\n    public func debugRawProbe(\n        cookieHeaderOverride: String? = nil,\n        manualCookieMode: Bool = false) async -> String\n    {\n        let stamp = ISO8601DateFormatter().string(from: Date())\n        var lines: [String] = []\n        lines.append(\"=== Ollama Debug Probe @ \\(stamp) ===\")\n        lines.append(\"\")\n\n        do {\n            let cookieHeader = try await self.resolveCookieHeader(\n                override: cookieHeaderOverride,\n                manualCookieMode: manualCookieMode,\n                logger: { msg in lines.append(\"[cookie] \\(msg)\") })\n            let diagnostics = RedirectDiagnostics(cookieHeader: cookieHeader, logger: nil)\n            let cookieNames = CookieHeaderNormalizer.pairs(from: cookieHeader).map(\\.name)\n            lines.append(\"Cookie names: \\(cookieNames.joined(separator: \", \"))\")\n\n            let (snapshot, responseInfo) = try await self.fetchWithDiagnostics(\n                cookieHeader: cookieHeader,\n                diagnostics: diagnostics)\n\n            lines.append(\"\")\n            lines.append(\"Fetch Success\")\n            lines.append(\"Status: \\(responseInfo.statusCode) \\(responseInfo.url)\")\n\n            if !diagnostics.redirects.isEmpty {\n                lines.append(\"\")\n                lines.append(\"Redirects:\")\n                for entry in diagnostics.redirects {\n                    lines.append(\"  \\(entry)\")\n                }\n            }\n\n            lines.append(\"\")\n            lines.append(\"Plan: \\(snapshot.planName ?? \"unknown\")\")\n            lines.append(\"Session: \\(snapshot.sessionUsedPercent?.description ?? \"nil\")%\")\n            lines.append(\"Weekly: \\(snapshot.weeklyUsedPercent?.description ?? \"nil\")%\")\n            lines.append(\"Session resetsAt: \\(snapshot.sessionResetsAt?.description ?? \"nil\")\")\n            lines.append(\"Weekly resetsAt: \\(snapshot.weeklyResetsAt?.description ?? \"nil\")\")\n\n            let output = lines.joined(separator: \"\\n\")\n            Task { @MainActor in Self.recordDump(output) }\n            return output\n        } catch {\n            lines.append(\"\")\n            lines.append(\"Probe Failed: \\(error.localizedDescription)\")\n            let output = lines.joined(separator: \"\\n\")\n            Task { @MainActor in Self.recordDump(output) }\n            return output\n        }\n    }\n\n    public static func latestDumps() async -> String {\n        await MainActor.run {\n            let result = Self.recentDumps.joined(separator: \"\\n\\n---\\n\\n\")\n            return result.isEmpty ? \"No Ollama probe dumps captured yet.\" : result\n        }\n    }\n\n    private func resolveCookieHeader(\n        override: String?,\n        manualCookieMode: Bool,\n        logger: ((String) -> Void)?) async throws -> String\n    {\n        if let manualHeader = try Self.resolveManualCookieHeader(\n            override: override,\n            manualCookieMode: manualCookieMode,\n            logger: logger)\n        {\n            return manualHeader\n        }\n        #if os(macOS)\n        let session = try OllamaCookieImporter.importSession(browserDetection: self.browserDetection, logger: logger)\n        logger?(\"[ollama] Using cookies from \\(session.sourceLabel)\")\n        return session.cookieHeader\n        #else\n        throw OllamaUsageError.noSessionCookie\n        #endif\n    }\n\n    static func resolveManualCookieHeader(\n        override: String?,\n        manualCookieMode: Bool,\n        logger: ((String) -> Void)? = nil) throws -> String?\n    {\n        if let override = CookieHeaderNormalizer.normalize(override) {\n            guard hasRecognizedOllamaSessionCookie(in: override) else {\n                logger?(\"[ollama] Manual cookie header missing recognized session cookie\")\n                throw OllamaUsageError.noSessionCookie\n            }\n            logger?(\"[ollama] Using manual cookie header\")\n            return override\n        }\n        if manualCookieMode {\n            throw OllamaUsageError.noSessionCookie\n        }\n        return nil\n    }\n\n    private func fetchWithDiagnostics(\n        cookieHeader: String,\n        diagnostics: RedirectDiagnostics,\n        now: Date = Date()) async throws -> (OllamaUsageSnapshot, ResponseInfo)\n    {\n        let (html, responseInfo) = try await self.fetchHTMLWithDiagnostics(\n            cookieHeader: cookieHeader,\n            diagnostics: diagnostics)\n        let snapshot = try OllamaUsageParser.parse(html: html, now: now)\n        return (snapshot, responseInfo)\n    }\n\n    private func fetchHTMLWithDiagnostics(\n        cookieHeader: String,\n        diagnostics: RedirectDiagnostics) async throws -> (String, ResponseInfo)\n    {\n        var request = URLRequest(url: Self.settingsURL)\n        request.httpMethod = \"GET\"\n        request.setValue(cookieHeader, forHTTPHeaderField: \"Cookie\")\n        request.setValue(\n            \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\",\n            forHTTPHeaderField: \"accept\")\n        request.setValue(\n            \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) \" +\n                \"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36\",\n            forHTTPHeaderField: \"user-agent\")\n        request.setValue(\"en-US,en;q=0.9\", forHTTPHeaderField: \"accept-language\")\n        request.setValue(\"https://ollama.com\", forHTTPHeaderField: \"origin\")\n        request.setValue(Self.settingsURL.absoluteString, forHTTPHeaderField: \"referer\")\n\n        let session = self.makeURLSession(diagnostics)\n        let (data, response) = try await session.data(for: request)\n        guard let httpResponse = response as? HTTPURLResponse else {\n            throw OllamaUsageError.networkError(\"Invalid response\")\n        }\n        let responseInfo = ResponseInfo(\n            statusCode: httpResponse.statusCode,\n            url: httpResponse.url?.absoluteString ?? \"unknown\")\n\n        guard httpResponse.statusCode == 200 else {\n            if httpResponse.statusCode == 401 || httpResponse.statusCode == 403 {\n                throw OllamaUsageError.invalidCredentials\n            }\n            throw OllamaUsageError.networkError(\"HTTP \\(httpResponse.statusCode)\")\n        }\n\n        let html = String(data: data, encoding: .utf8) ?? \"\"\n        return (html, responseInfo)\n    }\n\n    @MainActor private static func recordDump(_ text: String) {\n        if self.recentDumps.count >= 5 { self.recentDumps.removeFirst() }\n        self.recentDumps.append(text)\n    }\n\n    private final class RedirectDiagnostics: NSObject, URLSessionTaskDelegate, @unchecked Sendable {\n        private let cookieHeader: String\n        private let logger: ((String) -> Void)?\n        var redirects: [String] = []\n\n        init(cookieHeader: String, logger: ((String) -> Void)?) {\n            self.cookieHeader = cookieHeader\n            self.logger = logger\n        }\n\n        func urlSession(\n            _: URLSession,\n            task _: URLSessionTask,\n            willPerformHTTPRedirection response: HTTPURLResponse,\n            newRequest request: URLRequest,\n            completionHandler: @escaping (URLRequest?) -> Void)\n        {\n            let from = response.url?.absoluteString ?? \"unknown\"\n            let to = request.url?.absoluteString ?? \"unknown\"\n            self.redirects.append(\"\\(response.statusCode) \\(from) -> \\(to)\")\n            var updated = request\n            if OllamaUsageFetcher.shouldAttachCookie(to: request.url), !self.cookieHeader.isEmpty {\n                updated.setValue(self.cookieHeader, forHTTPHeaderField: \"Cookie\")\n            } else {\n                updated.setValue(nil, forHTTPHeaderField: \"Cookie\")\n            }\n            if let referer = response.url?.absoluteString {\n                updated.setValue(referer, forHTTPHeaderField: \"referer\")\n            }\n            if let logger {\n                logger(\"[ollama] Redirect \\(response.statusCode) \\(from) -> \\(to)\")\n            }\n            completionHandler(updated)\n        }\n    }\n\n    private struct ResponseInfo {\n        let statusCode: Int\n        let url: String\n    }\n\n    private func logDiagnostics(\n        responseInfo: ResponseInfo?,\n        diagnostics: RedirectDiagnostics,\n        logger: (String) -> Void)\n    {\n        if let responseInfo {\n            logger(\"[ollama] Response: \\(responseInfo.statusCode) \\(responseInfo.url)\")\n        }\n        if !diagnostics.redirects.isEmpty {\n            logger(\"[ollama] Redirects:\")\n            for entry in diagnostics.redirects {\n                logger(\"[ollama]   \\(entry)\")\n            }\n        }\n    }\n\n    private func logHTMLHints(html: String, logger: (String) -> Void) {\n        logger(\"[ollama] HTML length: \\(html.utf8.count) bytes\")\n        logger(\"[ollama] Contains Cloud Usage: \\(html.contains(\"Cloud Usage\"))\")\n        logger(\"[ollama] Contains Session usage: \\(html.contains(\"Session usage\"))\")\n        logger(\"[ollama] Contains Hourly usage: \\(html.contains(\"Hourly usage\"))\")\n        logger(\"[ollama] Contains Weekly usage: \\(html.contains(\"Weekly usage\"))\")\n    }\n\n    private func cookieNames(from header: String) -> [String] {\n        header.split(separator: \";\", omittingEmptySubsequences: false).compactMap { part in\n            let trimmed = part.trimmingCharacters(in: .whitespaces)\n            guard let idx = trimmed.firstIndex(of: \"=\") else { return nil }\n            let name = trimmed[..<idx]\n            return name.isEmpty ? nil : String(name)\n        }\n    }\n\n    static func shouldAttachCookie(to url: URL?) -> Bool {\n        guard let host = url?.host?.lowercased() else { return false }\n        if host == \"ollama.com\" || host == \"www.ollama.com\" { return true }\n        return host.hasSuffix(\".ollama.com\")\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Ollama/OllamaUsageParser.swift",
    "content": "import Foundation\n\nenum OllamaUsageParser {\n    private static let primaryUsageLabels = [\"Session usage\", \"Hourly usage\"]\n\n    enum ParseFailure: Equatable {\n        case notLoggedIn\n        case missingUsageData\n    }\n\n    enum ClassifiedParseResult {\n        case success(OllamaUsageSnapshot)\n        case failure(ParseFailure)\n    }\n\n    static func parse(html: String, now: Date = Date()) throws -> OllamaUsageSnapshot {\n        switch self.parseClassified(html: html, now: now) {\n        case let .success(snapshot):\n            return snapshot\n        case .failure(.notLoggedIn):\n            throw OllamaUsageError.notLoggedIn\n        case .failure(.missingUsageData):\n            throw OllamaUsageError.parseFailed(\"Missing Ollama usage data.\")\n        }\n    }\n\n    static func parseClassified(html: String, now: Date = Date()) -> ClassifiedParseResult {\n        let plan = self.parsePlanName(html)\n        let email = self.parseAccountEmail(html)\n        let session = self.parseUsageBlock(labels: self.primaryUsageLabels, html: html)\n        let weekly = self.parseUsageBlock(label: \"Weekly usage\", html: html)\n\n        if session == nil, weekly == nil {\n            if self.looksSignedOut(html) {\n                return .failure(.notLoggedIn)\n            }\n            return .failure(.missingUsageData)\n        }\n\n        return .success(OllamaUsageSnapshot(\n            planName: plan,\n            accountEmail: email,\n            sessionUsedPercent: session?.usedPercent,\n            weeklyUsedPercent: weekly?.usedPercent,\n            sessionResetsAt: session?.resetsAt,\n            weeklyResetsAt: weekly?.resetsAt,\n            updatedAt: now))\n    }\n\n    private struct UsageBlock {\n        let usedPercent: Double\n        let resetsAt: Date?\n    }\n\n    private static func parsePlanName(_ html: String) -> String? {\n        let pattern = #\"Cloud Usage\\s*</span>\\s*<span[^>]*>([^<]+)</span>\"#\n        guard let raw = self.firstCapture(in: html, pattern: pattern, options: [.dotMatchesLineSeparators])\n        else { return nil }\n        let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)\n        return trimmed.isEmpty ? nil : trimmed\n    }\n\n    private static func parseAccountEmail(_ html: String) -> String? {\n        let pattern = #\"id=\\\"header-email\\\"[^>]*>([^<]+)<\"#\n        guard let raw = self.firstCapture(in: html, pattern: pattern, options: [.dotMatchesLineSeparators])\n        else { return nil }\n        let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)\n        guard trimmed.contains(\"@\") else { return nil }\n        return trimmed\n    }\n\n    private static func parseUsageBlock(label: String, html: String) -> UsageBlock? {\n        guard let labelRange = html.range(of: label) else { return nil }\n        let tail = String(html[labelRange.upperBound...])\n        let window = String(tail.prefix(800))\n\n        guard let usedPercent = self.parsePercent(in: window) else { return nil }\n        let resetsAt = self.parseISODate(in: window)\n        return UsageBlock(usedPercent: usedPercent, resetsAt: resetsAt)\n    }\n\n    private static func parseUsageBlock(labels: [String], html: String) -> UsageBlock? {\n        for label in labels {\n            if let parsed = self.parseUsageBlock(label: label, html: html) {\n                return parsed\n            }\n        }\n        return nil\n    }\n\n    private static func parsePercent(in text: String) -> Double? {\n        let usedPattern = #\"([0-9]+(?:\\.[0-9]+)?)\\s*%\\s*used\"#\n        if let raw = self.firstCapture(in: text, pattern: usedPattern, options: [.caseInsensitive]) {\n            return Double(raw)\n        }\n        let widthPattern = #\"width:\\s*([0-9]+(?:\\.[0-9]+)?)%\"#\n        if let raw = self.firstCapture(in: text, pattern: widthPattern, options: [.caseInsensitive]) {\n            return Double(raw)\n        }\n        return nil\n    }\n\n    private static func parseISODate(in text: String) -> Date? {\n        let pattern = #\"data-time=\\\"([^\\\"]+)\\\"\"#\n        guard let raw = self.firstCapture(in: text, pattern: pattern, options: []) else { return nil }\n        let formatter = ISO8601DateFormatter()\n        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]\n        if let date = formatter.date(from: raw) {\n            return date\n        }\n        let fallback = ISO8601DateFormatter()\n        fallback.formatOptions = [.withInternetDateTime]\n        return fallback.date(from: raw)\n    }\n\n    private static func firstCapture(\n        in text: String,\n        pattern: String,\n        options: NSRegularExpression.Options) -> String?\n    {\n        guard let regex = try? NSRegularExpression(pattern: pattern, options: options) else { return nil }\n        return Self.performMatch(regex: regex, text: text)\n    }\n\n    private static func performMatch(\n        regex: NSRegularExpression,\n        text: String) -> String?\n    {\n        let range = NSRange(text.startIndex..<text.endIndex, in: text)\n        guard let match = regex.firstMatch(in: text, options: [], range: range), match.numberOfRanges > 1,\n              let captureRange = Range(match.range(at: 1), in: text)\n        else { return nil }\n        return String(text[captureRange])\n    }\n\n    private static func looksSignedOut(_ html: String) -> Bool {\n        let lower = html.lowercased()\n        let hasSignInHeading = lower.contains(\"sign in to ollama\") || lower.contains(\"log in to ollama\")\n        let hasAuthRoute = lower.contains(\"/api/auth/signin\") || lower.contains(\"/auth/signin\")\n        let hasLoginRoute = lower.contains(\"action=\\\"/login\\\"\")\n            || lower.contains(\"action='/login'\")\n            || lower.contains(\"href=\\\"/login\\\"\")\n            || lower.contains(\"href='/login'\")\n            || lower.contains(\"action=\\\"/signin\\\"\")\n            || lower.contains(\"action='/signin'\")\n            || lower.contains(\"href=\\\"/signin\\\"\")\n            || lower.contains(\"href='/signin'\")\n        let hasPasswordField = lower.contains(\"type=\\\"password\\\"\")\n            || lower.contains(\"type='password'\")\n            || lower.contains(\"name=\\\"password\\\"\")\n            || lower.contains(\"name='password'\")\n        let hasEmailField = lower.contains(\"type=\\\"email\\\"\")\n            || lower.contains(\"type='email'\")\n            || lower.contains(\"name=\\\"email\\\"\")\n            || lower.contains(\"name='email'\")\n        let hasAuthForm = lower.contains(\"<form\")\n        let hasAuthEndpoint = hasAuthRoute || hasLoginRoute\n\n        if hasSignInHeading, hasAuthForm, hasEmailField || hasPasswordField || hasAuthEndpoint {\n            return true\n        }\n        if hasAuthForm, hasAuthEndpoint {\n            return true\n        }\n        if hasAuthForm, hasPasswordField, hasEmailField {\n            return true\n        }\n        return false\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Ollama/OllamaUsageSnapshot.swift",
    "content": "import Foundation\n\npublic struct OllamaUsageSnapshot: Sendable {\n    public let planName: String?\n    public let accountEmail: String?\n    public let sessionUsedPercent: Double?\n    public let weeklyUsedPercent: Double?\n    public let sessionResetsAt: Date?\n    public let weeklyResetsAt: Date?\n    public let updatedAt: Date\n\n    public init(\n        planName: String?,\n        accountEmail: String?,\n        sessionUsedPercent: Double?,\n        weeklyUsedPercent: Double?,\n        sessionResetsAt: Date?,\n        weeklyResetsAt: Date?,\n        updatedAt: Date)\n    {\n        self.planName = planName\n        self.accountEmail = accountEmail\n        self.sessionUsedPercent = sessionUsedPercent\n        self.weeklyUsedPercent = weeklyUsedPercent\n        self.sessionResetsAt = sessionResetsAt\n        self.weeklyResetsAt = weeklyResetsAt\n        self.updatedAt = updatedAt\n    }\n}\n\nextension OllamaUsageSnapshot {\n    public func toUsageSnapshot() -> UsageSnapshot {\n        let sessionWindow = self.makeWindow(\n            usedPercent: self.sessionUsedPercent,\n            resetsAt: self.sessionResetsAt)\n        let weeklyWindow = self.makeWindow(\n            usedPercent: self.weeklyUsedPercent,\n            resetsAt: self.weeklyResetsAt)\n\n        let plan = self.planName?.trimmingCharacters(in: .whitespacesAndNewlines)\n        let email = self.accountEmail?.trimmingCharacters(in: .whitespacesAndNewlines)\n        let identity = ProviderIdentitySnapshot(\n            providerID: .ollama,\n            accountEmail: email?.isEmpty == false ? email : nil,\n            accountOrganization: nil,\n            loginMethod: plan?.isEmpty == false ? plan : nil)\n\n        return UsageSnapshot(\n            primary: sessionWindow,\n            secondary: weeklyWindow,\n            tertiary: nil,\n            providerCost: nil,\n            updatedAt: self.updatedAt,\n            identity: identity)\n    }\n\n    private func makeWindow(usedPercent: Double?, resetsAt: Date?) -> RateWindow? {\n        guard let usedPercent else { return nil }\n        let clamped = min(100, max(0, usedPercent))\n        return RateWindow(\n            usedPercent: clamped,\n            windowMinutes: nil,\n            resetsAt: resetsAt,\n            resetDescription: nil)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/OpenCode/OpenCodeCookieImporter.swift",
    "content": "import Foundation\n\n#if os(macOS)\nimport SweetCookieKit\n\nprivate let opencodeCookieImportOrder: BrowserCookieImportOrder =\n    ProviderDefaults.metadata[.opencode]?.browserCookieOrder ?? Browser.defaultImportOrder\n\npublic enum OpenCodeCookieImporter {\n    private static let cookieClient = BrowserCookieClient()\n    private static let cookieDomains = [\"opencode.ai\", \"app.opencode.ai\"]\n\n    public struct SessionInfo: Sendable {\n        public let cookies: [HTTPCookie]\n        public let sourceLabel: String\n\n        public init(cookies: [HTTPCookie], sourceLabel: String) {\n            self.cookies = cookies\n            self.sourceLabel = sourceLabel\n        }\n\n        public var cookieHeader: String {\n            self.cookies.map { \"\\($0.name)=\\($0.value)\" }.joined(separator: \"; \")\n        }\n    }\n\n    public static func importSession(\n        browserDetection: BrowserDetection,\n        preferredBrowsers: [Browser] = [.chrome],\n        logger: ((String) -> Void)? = nil) throws -> SessionInfo\n    {\n        let log: (String) -> Void = { msg in logger?(\"[opencode-cookie] \\(msg)\") }\n        let installedBrowsers = preferredBrowsers.isEmpty\n            ? opencodeCookieImportOrder.cookieImportCandidates(using: browserDetection)\n            : preferredBrowsers.cookieImportCandidates(using: browserDetection)\n\n        for browserSource in installedBrowsers {\n            do {\n                let query = BrowserCookieQuery(domains: self.cookieDomains)\n                let sources = try Self.cookieClient.records(\n                    matching: query,\n                    in: browserSource,\n                    logger: log)\n                for source in sources where !source.records.isEmpty {\n                    let httpCookies = BrowserCookieClient.makeHTTPCookies(source.records, origin: query.origin)\n                    if !httpCookies.isEmpty {\n                        let hasAuthCookie = httpCookies.contains { cookie in\n                            cookie.name == \"auth\" || cookie.name == \"__Host-auth\"\n                        }\n                        if !hasAuthCookie {\n                            log(\"Skipping \\(source.label) cookies: missing auth cookie\")\n                            continue\n                        }\n                        log(\"Found \\(httpCookies.count) OpenCode cookies in \\(source.label)\")\n                        return SessionInfo(cookies: httpCookies, sourceLabel: source.label)\n                    }\n                }\n            } catch {\n                BrowserCookieAccessGate.recordIfNeeded(error)\n                log(\"\\(browserSource.displayName) cookie import failed: \\(error.localizedDescription)\")\n            }\n        }\n\n        throw OpenCodeCookieImportError.noCookies\n    }\n\n    public static func hasSession(\n        browserDetection: BrowserDetection,\n        preferredBrowsers: [Browser] = [.chrome],\n        logger: ((String) -> Void)? = nil) -> Bool\n    {\n        do {\n            _ = try self.importSession(\n                browserDetection: browserDetection,\n                preferredBrowsers: preferredBrowsers,\n                logger: logger)\n            return true\n        } catch {\n            return false\n        }\n    }\n}\n\nenum OpenCodeCookieImportError: LocalizedError {\n    case noCookies\n\n    var errorDescription: String? {\n        switch self {\n        case .noCookies:\n            \"No OpenCode session cookies found in browsers.\"\n        }\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/OpenCode/OpenCodeProviderDescriptor.swift",
    "content": "import CodexBarMacroSupport\nimport Foundation\n\n@ProviderDescriptorRegistration\n@ProviderDescriptorDefinition\npublic enum OpenCodeProviderDescriptor {\n    static func makeDescriptor() -> ProviderDescriptor {\n        ProviderDescriptor(\n            id: .opencode,\n            metadata: ProviderMetadata(\n                id: .opencode,\n                displayName: \"OpenCode\",\n                sessionLabel: \"5-hour\",\n                weeklyLabel: \"Weekly\",\n                opusLabel: nil,\n                supportsOpus: false,\n                supportsCredits: false,\n                creditsHint: \"\",\n                toggleTitle: \"Show OpenCode usage\",\n                cliName: \"opencode\",\n                defaultEnabled: false,\n                isPrimaryProvider: false,\n                usesAccountFallback: false,\n                browserCookieOrder: ProviderBrowserCookieDefaults.defaultImportOrder,\n                dashboardURL: \"https://opencode.ai\",\n                statusPageURL: nil),\n            branding: ProviderBranding(\n                iconStyle: .opencode,\n                iconResourceName: \"ProviderIcon-opencode\",\n                color: ProviderColor(red: 59 / 255, green: 130 / 255, blue: 246 / 255)),\n            tokenCost: ProviderTokenCostConfig(\n                supportsTokenCost: false,\n                noDataMessage: { \"OpenCode cost summary is not supported.\" }),\n            fetchPlan: ProviderFetchPlan(\n                sourceModes: [.auto, .web],\n                pipeline: ProviderFetchPipeline(resolveStrategies: { _ in [OpenCodeUsageFetchStrategy()] })),\n            cli: ProviderCLIConfig(\n                name: \"opencode\",\n                versionDetector: nil))\n    }\n}\n\nstruct OpenCodeUsageFetchStrategy: ProviderFetchStrategy {\n    let id: String = \"opencode.web\"\n    let kind: ProviderFetchKind = .web\n\n    func isAvailable(_ context: ProviderFetchContext) async -> Bool {\n        guard context.settings?.opencode?.cookieSource != .off else { return false }\n        return true\n    }\n\n    func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {\n        let workspaceOverride = context.settings?.opencode?.workspaceID\n            ?? context.env[\"CODEXBAR_OPENCODE_WORKSPACE_ID\"]\n        let cookieSource = context.settings?.opencode?.cookieSource ?? .auto\n        do {\n            let cookieHeader = try Self.resolveCookieHeader(context: context, allowCached: true)\n            let snapshot = try await OpenCodeUsageFetcher.fetchUsage(\n                cookieHeader: cookieHeader,\n                timeout: context.webTimeout,\n                workspaceIDOverride: workspaceOverride)\n            return self.makeResult(\n                usage: snapshot.toUsageSnapshot(),\n                sourceLabel: \"web\")\n        } catch OpenCodeUsageError.invalidCredentials where cookieSource != .manual {\n            #if os(macOS)\n            CookieHeaderCache.clear(provider: .opencode)\n            let cookieHeader = try Self.resolveCookieHeader(context: context, allowCached: false)\n            let snapshot = try await OpenCodeUsageFetcher.fetchUsage(\n                cookieHeader: cookieHeader,\n                timeout: context.webTimeout,\n                workspaceIDOverride: workspaceOverride)\n            return self.makeResult(\n                usage: snapshot.toUsageSnapshot(),\n                sourceLabel: \"web\")\n            #else\n            throw OpenCodeUsageError.invalidCredentials\n            #endif\n        }\n    }\n\n    func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool {\n        false\n    }\n\n    private static func resolveCookieHeader(context: ProviderFetchContext, allowCached: Bool) throws -> String {\n        if let settings = context.settings?.opencode, settings.cookieSource == .manual {\n            if let header = CookieHeaderNormalizer.normalize(settings.manualCookieHeader) {\n                let pairs = CookieHeaderNormalizer.pairs(from: header)\n                let hasAuthCookie = pairs.contains { pair in\n                    pair.name == \"auth\" || pair.name == \"__Host-auth\"\n                }\n                if hasAuthCookie {\n                    return header\n                }\n            }\n            throw OpenCodeSettingsError.invalidCookie\n        }\n\n        #if os(macOS)\n        if allowCached,\n           let cached = CookieHeaderCache.load(provider: .opencode),\n           !cached.cookieHeader.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty\n        {\n            return cached.cookieHeader\n        }\n        let session = try OpenCodeCookieImporter.importSession(browserDetection: context.browserDetection)\n        CookieHeaderCache.store(\n            provider: .opencode,\n            cookieHeader: session.cookieHeader,\n            sourceLabel: session.sourceLabel)\n        return session.cookieHeader\n        #else\n        throw OpenCodeSettingsError.missingCookie\n        #endif\n    }\n}\n\nenum OpenCodeSettingsError: LocalizedError {\n    case missingCookie\n    case invalidCookie\n\n    var errorDescription: String? {\n        switch self {\n        case .missingCookie:\n            \"No OpenCode session cookies found in browsers.\"\n        case .invalidCookie:\n            \"OpenCode cookie header is invalid.\"\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/OpenCode/OpenCodeUsageFetcher.swift",
    "content": "import Foundation\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n\npublic enum OpenCodeUsageError: LocalizedError {\n    case invalidCredentials\n    case networkError(String)\n    case apiError(String)\n    case parseFailed(String)\n\n    public var errorDescription: String? {\n        switch self {\n        case .invalidCredentials:\n            \"OpenCode session cookie is invalid or expired.\"\n        case let .networkError(message):\n            \"OpenCode network error: \\(message)\"\n        case let .apiError(message):\n            \"OpenCode API error: \\(message)\"\n        case let .parseFailed(message):\n            \"OpenCode parse error: \\(message)\"\n        }\n    }\n}\n\npublic struct OpenCodeUsageFetcher: Sendable {\n    private static let log = CodexBarLog.logger(LogCategories.opencodeUsage)\n    private static let baseURL = URL(string: \"https://opencode.ai\")!\n    private static let serverURL = URL(string: \"https://opencode.ai/_server\")!\n    private static let workspacesServerID = \"def39973159c7f0483d8793a822b8dbb10d067e12c65455fcb4608459ba0234f\"\n    private static let subscriptionServerID = \"7abeebee372f304e050aaaf92be863f4a86490e382f8c79db68fd94040d691b4\"\n    private static let percentKeys = [\n        \"usagePercent\",\n        \"usedPercent\",\n        \"percentUsed\",\n        \"percent\",\n        \"usage_percent\",\n        \"used_percent\",\n        \"utilization\",\n        \"utilizationPercent\",\n        \"utilization_percent\",\n        \"usage\",\n    ]\n    private static let resetInKeys = [\n        \"resetInSec\",\n        \"resetInSeconds\",\n        \"resetSeconds\",\n        \"reset_sec\",\n        \"reset_in_sec\",\n        \"resetsInSec\",\n        \"resetsInSeconds\",\n        \"resetIn\",\n        \"resetSec\",\n    ]\n    private static let resetAtKeys = [\n        \"resetAt\",\n        \"resetsAt\",\n        \"reset_at\",\n        \"resets_at\",\n        \"nextReset\",\n        \"next_reset\",\n        \"renewAt\",\n        \"renew_at\",\n    ]\n    private static func makeISO8601Formatter() -> ISO8601DateFormatter {\n        let formatter = ISO8601DateFormatter()\n        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]\n        return formatter\n    }\n\n    private static let userAgent =\n        \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) \" +\n        \"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36\"\n\n    private struct ServerRequest {\n        let serverID: String\n        let args: [Any]?\n        let method: String\n        let referer: URL\n    }\n\n    public static func fetchUsage(\n        cookieHeader: String,\n        timeout: TimeInterval,\n        now: Date = Date(),\n        workspaceIDOverride: String? = nil) async throws -> OpenCodeUsageSnapshot\n    {\n        let workspaceID: String = if let override = self.normalizeWorkspaceID(workspaceIDOverride) {\n            override\n        } else {\n            try await self.fetchWorkspaceID(\n                cookieHeader: cookieHeader,\n                timeout: timeout)\n        }\n        let subscriptionText = try await self.fetchSubscriptionInfo(\n            workspaceID: workspaceID,\n            cookieHeader: cookieHeader,\n            timeout: timeout)\n        return try self.parseSubscription(text: subscriptionText, now: now)\n    }\n\n    private static func fetchWorkspaceID(\n        cookieHeader: String,\n        timeout: TimeInterval) async throws -> String\n    {\n        let text = try await self.fetchServerText(\n            request: ServerRequest(\n                serverID: self.workspacesServerID,\n                args: nil,\n                method: \"GET\",\n                referer: self.baseURL),\n            cookieHeader: cookieHeader,\n            timeout: timeout)\n        if self.looksSignedOut(text: text) {\n            throw OpenCodeUsageError.invalidCredentials\n        }\n        var ids = self.parseWorkspaceIDs(text: text)\n        if ids.isEmpty {\n            ids = self.parseWorkspaceIDsFromJSON(text: text)\n        }\n        if ids.isEmpty {\n            Self.log.error(\"OpenCode workspace ids missing after GET; retrying with POST.\")\n            let fallback = try await self.fetchServerText(\n                request: ServerRequest(\n                    serverID: self.workspacesServerID,\n                    args: [],\n                    method: \"POST\",\n                    referer: self.baseURL),\n                cookieHeader: cookieHeader,\n                timeout: timeout)\n            if self.looksSignedOut(text: fallback) {\n                throw OpenCodeUsageError.invalidCredentials\n            }\n            ids = self.parseWorkspaceIDs(text: fallback)\n            if ids.isEmpty {\n                ids = self.parseWorkspaceIDsFromJSON(text: fallback)\n            }\n            if ids.isEmpty {\n                self.logParseSummary(text: fallback)\n                throw OpenCodeUsageError.parseFailed(\"Missing workspace id.\")\n            }\n            return ids[0]\n        }\n        return ids[0]\n    }\n\n    private static func fetchSubscriptionInfo(\n        workspaceID: String,\n        cookieHeader: String,\n        timeout: TimeInterval) async throws -> String\n    {\n        let referer = URL(string: \"https://opencode.ai/workspace/\\(workspaceID)/billing\") ?? self.baseURL\n        let text = try await self.fetchServerText(\n            request: ServerRequest(\n                serverID: self.subscriptionServerID,\n                args: [workspaceID],\n                method: \"GET\",\n                referer: referer),\n            cookieHeader: cookieHeader,\n            timeout: timeout)\n        if self.looksSignedOut(text: text) {\n            throw OpenCodeUsageError.invalidCredentials\n        }\n        if self.isExplicitNullPayload(text: text) {\n            Self.log.warning(\"OpenCode subscription GET returned null; skipping POST fallback.\")\n            throw self.missingSubscriptionDataError(workspaceID: workspaceID)\n        }\n        if self.parseSubscriptionJSON(text: text, now: Date()) == nil,\n           self.extractDouble(\n               pattern: #\"rollingUsage[^}]*?usagePercent\\s*:\\s*([0-9]+(?:\\.[0-9]+)?)\"#,\n               text: text) == nil\n        {\n            Self.log.error(\"OpenCode subscription payload missing after GET; retrying with POST.\")\n            let fallback = try await self.fetchServerText(\n                request: ServerRequest(\n                    serverID: self.subscriptionServerID,\n                    args: [workspaceID],\n                    method: \"POST\",\n                    referer: referer),\n                cookieHeader: cookieHeader,\n                timeout: timeout)\n            if self.looksSignedOut(text: fallback) {\n                throw OpenCodeUsageError.invalidCredentials\n            }\n            if self.isExplicitNullPayload(text: fallback) {\n                Self.log.warning(\"OpenCode subscription POST returned null.\")\n                throw self.missingSubscriptionDataError(workspaceID: workspaceID)\n            }\n            return fallback\n        }\n        return text\n    }\n\n    private static func isExplicitNullPayload(text: String) -> Bool {\n        let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)\n        if trimmed.caseInsensitiveCompare(\"null\") == .orderedSame {\n            return true\n        }\n        guard let data = trimmed.data(using: .utf8),\n              let object = try? JSONSerialization.jsonObject(with: data, options: [])\n        else {\n            return false\n        }\n        return object is NSNull\n    }\n\n    private static func missingSubscriptionDataError(workspaceID: String) -> OpenCodeUsageError {\n        OpenCodeUsageError.apiError(\n            \"No subscription usage data was returned for workspace \\(workspaceID). \" +\n                \"This usually means this workspace does not have OpenCode Black usage data.\")\n    }\n\n    private static func normalizeWorkspaceID(_ raw: String?) -> String? {\n        guard let raw else { return nil }\n        let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)\n        if trimmed.hasPrefix(\"wrk_\"), trimmed.count > 4 {\n            return trimmed\n        }\n        if let url = URL(string: trimmed) {\n            let parts = url.pathComponents\n            if let index = parts.firstIndex(of: \"workspace\"),\n               parts.count > index + 1\n            {\n                let candidate = parts[index + 1]\n                if candidate.hasPrefix(\"wrk_\"), candidate.count > 4 {\n                    return candidate\n                }\n            }\n        }\n        if let match = trimmed.range(of: #\"wrk_[A-Za-z0-9]+\"#, options: .regularExpression) {\n            return String(trimmed[match])\n        }\n        return nil\n    }\n\n    private static func fetchServerText(\n        request serverRequest: ServerRequest,\n        cookieHeader: String,\n        timeout: TimeInterval) async throws -> String\n    {\n        let url = self.serverRequestURL(\n            serverID: serverRequest.serverID,\n            args: serverRequest.args,\n            method: serverRequest.method)\n        var urlRequest = URLRequest(url: url)\n        urlRequest.httpMethod = serverRequest.method\n        urlRequest.timeoutInterval = timeout\n        urlRequest.setValue(cookieHeader, forHTTPHeaderField: \"Cookie\")\n        urlRequest.setValue(serverRequest.serverID, forHTTPHeaderField: \"X-Server-Id\")\n        urlRequest.setValue(\"server-fn:\\(UUID().uuidString)\", forHTTPHeaderField: \"X-Server-Instance\")\n        urlRequest.setValue(self.userAgent, forHTTPHeaderField: \"User-Agent\")\n        urlRequest.setValue(self.baseURL.absoluteString, forHTTPHeaderField: \"Origin\")\n        urlRequest.setValue(serverRequest.referer.absoluteString, forHTTPHeaderField: \"Referer\")\n        urlRequest.setValue(\"text/javascript, application/json;q=0.9, */*;q=0.8\", forHTTPHeaderField: \"Accept\")\n        if serverRequest.method.uppercased() != \"GET\",\n           let args = serverRequest.args\n        {\n            let body = try JSONSerialization.data(withJSONObject: args, options: [])\n            urlRequest.httpBody = body\n            urlRequest.setValue(\"application/json\", forHTTPHeaderField: \"Content-Type\")\n        }\n\n        let (data, response) = try await URLSession.shared.data(for: urlRequest)\n        guard let httpResponse = response as? HTTPURLResponse else {\n            throw OpenCodeUsageError.networkError(\"Invalid response\")\n        }\n\n        guard httpResponse.statusCode == 200 else {\n            let bodyText = String(data: data, encoding: .utf8) ?? \"\"\n            let contentType = httpResponse.value(forHTTPHeaderField: \"Content-Type\") ?? \"unknown\"\n            Self.log.error(\"OpenCode returned \\(httpResponse.statusCode) (type=\\(contentType) length=\\(data.count))\")\n            if self.looksSignedOut(text: bodyText) {\n                throw OpenCodeUsageError.invalidCredentials\n            }\n            if httpResponse.statusCode == 401 || httpResponse.statusCode == 403 {\n                throw OpenCodeUsageError.invalidCredentials\n            }\n            if let message = self.extractServerErrorMessage(from: bodyText) {\n                throw OpenCodeUsageError.apiError(\"HTTP \\(httpResponse.statusCode): \\(message)\")\n            }\n            throw OpenCodeUsageError.apiError(\"HTTP \\(httpResponse.statusCode)\")\n        }\n\n        guard let text = String(data: data, encoding: .utf8) else {\n            throw OpenCodeUsageError.parseFailed(\"Response was not UTF-8.\")\n        }\n        return text\n    }\n\n    static func parseSubscription(text: String, now: Date) throws -> OpenCodeUsageSnapshot {\n        if let snapshot = self.parseSubscriptionJSON(text: text, now: now) {\n            return snapshot\n        }\n\n        guard let rollingPercent = self.extractDouble(\n            pattern: #\"rollingUsage[^}]*?usagePercent\\s*:\\s*([0-9]+(?:\\.[0-9]+)?)\"#,\n            text: text),\n            let rollingReset = self.extractInt(\n                pattern: #\"rollingUsage[^}]*?resetInSec\\s*:\\s*([0-9]+)\"#,\n                text: text),\n            let weeklyPercent = self.extractDouble(\n                pattern: #\"weeklyUsage[^}]*?usagePercent\\s*:\\s*([0-9]+(?:\\.[0-9]+)?)\"#,\n                text: text),\n            let weeklyReset = self.extractInt(\n                pattern: #\"weeklyUsage[^}]*?resetInSec\\s*:\\s*([0-9]+)\"#,\n                text: text)\n        else {\n            self.logParseSummary(text: text)\n            throw OpenCodeUsageError.parseFailed(\"Missing usage fields.\")\n        }\n\n        return OpenCodeUsageSnapshot(\n            rollingUsagePercent: rollingPercent,\n            weeklyUsagePercent: weeklyPercent,\n            rollingResetInSec: rollingReset,\n            weeklyResetInSec: weeklyReset,\n            updatedAt: now)\n    }\n\n    private static func parseSubscriptionJSON(text: String, now: Date) -> OpenCodeUsageSnapshot? {\n        guard let data = text.data(using: .utf8),\n              let object = try? JSONSerialization.jsonObject(with: data, options: [])\n        else {\n            return nil\n        }\n\n        if let snapshot = self.parseUsageJSON(object: object, now: now) {\n            return snapshot\n        }\n\n        if let snapshot = self.parseUsageFromCandidates(object: object, now: now) {\n            return snapshot\n        }\n\n        self.logParseSummary(object: object)\n        return nil\n    }\n\n    static func parseWorkspaceIDs(text: String) -> [String] {\n        let pattern = #\"id\\s*:\\s*\\\"(wrk_[^\\\"]+)\\\"\"#\n        guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { return [] }\n        let nsrange = NSRange(text.startIndex..<text.endIndex, in: text)\n        return regex.matches(in: text, options: [], range: nsrange).compactMap { match in\n            guard let range = Range(match.range(at: 1), in: text) else { return nil }\n            return String(text[range])\n        }\n    }\n\n    private static func parseWorkspaceIDsFromJSON(text: String) -> [String] {\n        guard let data = text.data(using: .utf8),\n              let object = try? JSONSerialization.jsonObject(with: data, options: [])\n        else {\n            return []\n        }\n        var results: [String] = []\n        self.collectWorkspaceIDs(object: object, out: &results)\n        return results\n    }\n\n    private static func collectWorkspaceIDs(object: Any, out: inout [String]) {\n        if let dict = object as? [String: Any] {\n            for (_, value) in dict {\n                self.collectWorkspaceIDs(object: value, out: &out)\n            }\n            return\n        }\n        if let array = object as? [Any] {\n            for value in array {\n                self.collectWorkspaceIDs(object: value, out: &out)\n            }\n            return\n        }\n        if let string = object as? String,\n           string.hasPrefix(\"wrk_\"),\n           !out.contains(string)\n        {\n            out.append(string)\n        }\n    }\n\n    private static func extractDouble(pattern: String, text: String) -> Double? {\n        guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { return nil }\n        let nsrange = NSRange(text.startIndex..<text.endIndex, in: text)\n        guard let match = regex.firstMatch(in: text, options: [], range: nsrange),\n              let range = Range(match.range(at: 1), in: text)\n        else {\n            return nil\n        }\n        return Double(text[range])\n    }\n\n    private static func extractInt(pattern: String, text: String) -> Int? {\n        guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { return nil }\n        let nsrange = NSRange(text.startIndex..<text.endIndex, in: text)\n        guard let match = regex.firstMatch(in: text, options: [], range: nsrange),\n              let range = Range(match.range(at: 1), in: text)\n        else {\n            return nil\n        }\n        return Int(text[range])\n    }\n\n    private static func doubleValue(from value: Any?) -> Double? {\n        switch value {\n        case let number as Double:\n            number\n        case let number as NSNumber:\n            number.doubleValue\n        case let string as String:\n            Double(string.trimmingCharacters(in: .whitespacesAndNewlines))\n        default:\n            nil\n        }\n    }\n\n    private static func intValue(from value: Any?) -> Int? {\n        switch value {\n        case let number as Int:\n            number\n        case let number as NSNumber:\n            number.intValue\n        case let string as String:\n            Int(string.trimmingCharacters(in: .whitespacesAndNewlines))\n        default:\n            nil\n        }\n    }\n\n    private static func looksSignedOut(text: String) -> Bool {\n        let lower = text.lowercased()\n        if lower.contains(\"login\") || lower.contains(\"sign in\") || lower.contains(\"auth/authorize\") {\n            return true\n        }\n        return false\n    }\n\n    private static func extractServerErrorMessage(from text: String) -> String? {\n        guard let data = text.data(using: .utf8),\n              let object = try? JSONSerialization.jsonObject(with: data, options: [])\n        else {\n            // If it's not JSON, try to extract error from HTML if possible\n            if let match = text.range(of: #\"(?i)<title>([^<]+)</title>\"#, options: .regularExpression) {\n                return String(text[match].dropFirst(7).dropLast(8)).trimmingCharacters(in: .whitespacesAndNewlines)\n            }\n            return nil\n        }\n\n        guard let dict = object as? [String: Any] else { return nil }\n\n        if let message = dict[\"message\"] as? String, !message.isEmpty {\n            return message\n        }\n        if let error = dict[\"error\"] as? String, !error.isEmpty {\n            return error\n        }\n        // Check for common error fields in some frameworks\n        if let detail = dict[\"detail\"] as? String, !detail.isEmpty {\n            return detail\n        }\n        return nil\n    }\n\n    private static func serverRequestURL(serverID: String, args: [Any]?, method: String) -> URL {\n        guard method.uppercased() == \"GET\" else {\n            return self.serverURL\n        }\n\n        var components = URLComponents(url: self.serverURL, resolvingAgainstBaseURL: false)\n        var queryItems = [URLQueryItem(name: \"id\", value: serverID)]\n        if let args, !args.isEmpty,\n           let data = try? JSONSerialization.data(withJSONObject: args, options: []),\n           let encodedArgs = String(data: data, encoding: .utf8)\n        {\n            queryItems.append(URLQueryItem(name: \"args\", value: encodedArgs))\n        }\n        components?.queryItems = queryItems\n        return components?.url ?? self.serverURL\n    }\n\n    private static func parseUsageJSON(object: Any, now: Date) -> OpenCodeUsageSnapshot? {\n        guard let dict = object as? [String: Any] else { return nil }\n        if let snapshot = self.parseUsageDictionary(dict, now: now) {\n            return snapshot\n        }\n\n        for key in [\"data\", \"result\", \"usage\", \"billing\", \"payload\"] {\n            if let nested = dict[key] as? [String: Any],\n               let snapshot = self.parseUsageDictionary(nested, now: now)\n            {\n                return snapshot\n            }\n        }\n\n        return self.parseUsageNested(dict, now: now, depth: 0)\n    }\n\n    private static func parseUsageDictionary(_ dict: [String: Any], now: Date) -> OpenCodeUsageSnapshot? {\n        if let usage = dict[\"usage\"] as? [String: Any],\n           let snapshot = self.parseUsageDictionary(usage, now: now)\n        {\n            return snapshot\n        }\n\n        let rollingKeys = [\"rollingUsage\", \"rolling\", \"rolling_usage\", \"rollingWindow\", \"rolling_window\"]\n        let weeklyKeys = [\"weeklyUsage\", \"weekly\", \"weekly_usage\", \"weeklyWindow\", \"weekly_window\"]\n\n        let rolling = rollingKeys.compactMap { dict[$0] as? [String: Any] }.first\n        let weekly = weeklyKeys.compactMap { dict[$0] as? [String: Any] }.first\n\n        if let rolling, let weekly {\n            return self.buildSnapshot(rolling: rolling, weekly: weekly, now: now)\n        }\n\n        return nil\n    }\n\n    private static func parseUsageNested(_ dict: [String: Any], now: Date, depth: Int) -> OpenCodeUsageSnapshot? {\n        if depth > 3 { return nil }\n        var rolling: [String: Any]?\n        var weekly: [String: Any]?\n\n        for (key, value) in dict {\n            guard let sub = value as? [String: Any] else { continue }\n            let lower = key.lowercased()\n            if lower.contains(\"rolling\") {\n                rolling = sub\n            } else if lower.contains(\"weekly\") || lower.contains(\"week\") {\n                weekly = sub\n            }\n        }\n\n        if let rolling, let weekly,\n           let snapshot = self.buildSnapshot(rolling: rolling, weekly: weekly, now: now)\n        {\n            return snapshot\n        }\n\n        for value in dict.values {\n            if let sub = value as? [String: Any],\n               let snapshot = self.parseUsageNested(sub, now: now, depth: depth + 1)\n            {\n                return snapshot\n            }\n        }\n\n        return nil\n    }\n\n    private static func parseUsageFromCandidates(object: Any, now: Date) -> OpenCodeUsageSnapshot? {\n        let candidates = self.collectWindowCandidates(object: object, now: now)\n        guard !candidates.isEmpty else { return nil }\n\n        let rollingCandidates = candidates.filter { candidate in\n            candidate.pathLower.contains(\"rolling\") ||\n                candidate.pathLower.contains(\"hour\") ||\n                candidate.pathLower.contains(\"5h\") ||\n                candidate.pathLower.contains(\"5-hour\")\n        }\n        let weeklyCandidates = candidates.filter { candidate in\n            candidate.pathLower.contains(\"weekly\") ||\n                candidate.pathLower.contains(\"week\")\n        }\n\n        let rolling = self.pickCandidate(\n            preferred: rollingCandidates,\n            fallback: candidates,\n            pickShorter: true)\n        let weekly = self.pickCandidate(\n            preferred: weeklyCandidates,\n            fallback: candidates,\n            pickShorter: false,\n            excluding: rolling?.id)\n\n        guard let rolling, let weekly else { return nil }\n\n        return OpenCodeUsageSnapshot(\n            rollingUsagePercent: rolling.percent,\n            weeklyUsagePercent: weekly.percent,\n            rollingResetInSec: rolling.resetInSec,\n            weeklyResetInSec: weekly.resetInSec,\n            updatedAt: now)\n    }\n\n    private struct WindowCandidate {\n        let id: UUID\n        let percent: Double\n        let resetInSec: Int\n        let pathLower: String\n    }\n\n    private static func collectWindowCandidates(object: Any, now: Date) -> [WindowCandidate] {\n        var candidates: [WindowCandidate] = []\n        self.collectWindowCandidates(object: object, now: now, path: [], out: &candidates)\n        return candidates\n    }\n\n    private static func collectWindowCandidates(\n        object: Any,\n        now: Date,\n        path: [String],\n        out: inout [WindowCandidate])\n    {\n        if let dict = object as? [String: Any] {\n            if let window = self.parseWindow(dict, now: now) {\n                let pathLower = path.joined(separator: \".\").lowercased()\n                out.append(WindowCandidate(\n                    id: UUID(),\n                    percent: window.percent,\n                    resetInSec: window.resetInSec,\n                    pathLower: pathLower))\n            }\n            for (key, value) in dict {\n                self.collectWindowCandidates(object: value, now: now, path: path + [key], out: &out)\n            }\n            return\n        }\n\n        if let array = object as? [Any] {\n            for (index, value) in array.enumerated() {\n                self.collectWindowCandidates(\n                    object: value,\n                    now: now,\n                    path: path + [\"[\\(index)]\"],\n                    out: &out)\n            }\n        }\n    }\n\n    private static func pickCandidate(\n        preferred: [WindowCandidate],\n        fallback: [WindowCandidate],\n        pickShorter: Bool,\n        excluding excluded: UUID? = nil) -> WindowCandidate?\n    {\n        let filteredPreferred = preferred.filter { $0.id != excluded }\n        if let picked = self.pickCandidate(from: filteredPreferred, pickShorter: pickShorter) {\n            return picked\n        }\n        let filteredFallback = fallback.filter { $0.id != excluded }\n        return self.pickCandidate(from: filteredFallback, pickShorter: pickShorter)\n    }\n\n    private static func pickCandidate(from candidates: [WindowCandidate], pickShorter: Bool) -> WindowCandidate? {\n        guard !candidates.isEmpty else { return nil }\n        let comparator: (WindowCandidate, WindowCandidate) -> Bool = { lhs, rhs in\n            if pickShorter {\n                if lhs.resetInSec == rhs.resetInSec { return lhs.percent > rhs.percent }\n                return lhs.resetInSec < rhs.resetInSec\n            }\n            if lhs.resetInSec == rhs.resetInSec { return lhs.percent > rhs.percent }\n            return lhs.resetInSec > rhs.resetInSec\n        }\n        return candidates.min(by: comparator)\n    }\n\n    private static func buildSnapshot(\n        rolling: [String: Any],\n        weekly: [String: Any],\n        now: Date) -> OpenCodeUsageSnapshot?\n    {\n        guard let rollingWindow = self.parseWindow(rolling, now: now),\n              let weeklyWindow = self.parseWindow(weekly, now: now)\n        else {\n            return nil\n        }\n\n        return OpenCodeUsageSnapshot(\n            rollingUsagePercent: rollingWindow.percent,\n            weeklyUsagePercent: weeklyWindow.percent,\n            rollingResetInSec: rollingWindow.resetInSec,\n            weeklyResetInSec: weeklyWindow.resetInSec,\n            updatedAt: now)\n    }\n\n    private static func parseWindow(_ dict: [String: Any], now: Date) -> (percent: Double, resetInSec: Int)? {\n        var percent = self.doubleValue(from: dict, keys: self.percentKeys)\n\n        if percent == nil {\n            let used = self.doubleValue(from: dict, keys: [\"used\", \"usage\", \"consumed\", \"count\", \"usedTokens\"])\n            let limit = self.doubleValue(from: dict, keys: [\"limit\", \"total\", \"quota\", \"max\", \"cap\", \"tokenLimit\"])\n            if let used, let limit, limit > 0 {\n                percent = (used / limit) * 100\n            }\n        }\n\n        guard var resolvedPercent = percent else { return nil }\n        if resolvedPercent <= 1.0, resolvedPercent >= 0 {\n            resolvedPercent *= 100\n        }\n        resolvedPercent = max(0, min(100, resolvedPercent))\n\n        var resetInSec = self.intValue(from: dict, keys: self.resetInKeys)\n        if resetInSec == nil {\n            let resetAtValue = self.value(from: dict, keys: self.resetAtKeys)\n            if let resetAt = self.dateValue(from: resetAtValue) {\n                resetInSec = max(0, Int(resetAt.timeIntervalSince(now)))\n            }\n        }\n\n        let resolvedReset = max(0, resetInSec ?? 0)\n        return (resolvedPercent, resolvedReset)\n    }\n\n    private static func doubleValue(from dict: [String: Any], keys: [String]) -> Double? {\n        for key in keys {\n            if let value = self.doubleValue(from: dict[key]) {\n                return value\n            }\n        }\n        return nil\n    }\n\n    private static func intValue(from dict: [String: Any], keys: [String]) -> Int? {\n        for key in keys {\n            if let value = self.intValue(from: dict[key]) {\n                return value\n            }\n        }\n        return nil\n    }\n\n    private static func value(from dict: [String: Any], keys: [String]) -> Any? {\n        for key in keys {\n            if let value = dict[key] {\n                return value\n            }\n        }\n        return nil\n    }\n\n    private static func dateValue(from value: Any?) -> Date? {\n        guard let value else { return nil }\n        if let number = self.doubleValue(from: value) {\n            if number > 1_000_000_000_000 {\n                return Date(timeIntervalSince1970: number / 1000)\n            }\n            if number > 1_000_000_000 {\n                return Date(timeIntervalSince1970: number)\n            }\n        }\n        if let string = value as? String {\n            if let number = Double(string.trimmingCharacters(in: .whitespacesAndNewlines)) {\n                return self.dateValue(from: number)\n            }\n            if let parsed = self.makeISO8601Formatter().date(from: string) {\n                return parsed\n            }\n        }\n        return nil\n    }\n\n    private static func logParseSummary(text: String) {\n        let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)\n        guard let data = text.data(using: .utf8),\n              let object = try? JSONSerialization.jsonObject(with: data, options: [])\n        else {\n            let hint = if trimmed.hasPrefix(\"<\") {\n                \"html\"\n            } else if trimmed.hasPrefix(\"{\") || trimmed.hasPrefix(\"[\") {\n                \"json\"\n            } else if trimmed.isEmpty {\n                \"empty\"\n            } else {\n                \"text\"\n            }\n            Self.log.error(\"OpenCode response non-JSON: hint=\\(hint) length=\\(text.count)\")\n            return\n        }\n        self.logParseSummary(object: object)\n    }\n\n    private static func logParseSummary(object: Any) {\n        let summary = self.summarizeJSON(object: object, depth: 0)\n        guard !summary.isEmpty else { return }\n        Self.log.error(\"OpenCode response summary: \\(summary)\")\n    }\n\n    private static func summarizeJSON(object: Any, depth: Int) -> String {\n        if depth > 3 { return \"\" }\n        if let dict = object as? [String: Any] {\n            let keys = dict.keys.sorted()\n            var parts: [String] = []\n            for key in keys {\n                let value = dict[key]\n                let type = self.valueTypeDescription(value, depth: depth + 1)\n                parts.append(\"\\(key):\\(type)\")\n            }\n            return \"{\\(parts.joined(separator: \", \"))}\"\n        }\n        if let array = object as? [Any] {\n            guard let first = array.first else { return \"[]\" }\n            let type = self.valueTypeDescription(first, depth: depth + 1)\n            return \"[\\(type)]\"\n        }\n        return self.scalarTypeDescription(object)\n    }\n\n    private static func valueTypeDescription(_ value: Any?, depth: Int) -> String {\n        guard let value else { return \"null\" }\n        if let dict = value as? [String: Any] {\n            return self.summarizeJSON(object: dict, depth: depth)\n        }\n        if let array = value as? [Any] {\n            return self.summarizeJSON(object: array, depth: depth)\n        }\n        return self.scalarTypeDescription(value)\n    }\n\n    private static func scalarTypeDescription(_ value: Any) -> String {\n        switch value {\n        case is String: \"string\"\n        case is Bool: \"bool\"\n        case is Int, is Double, is NSNumber: \"number\"\n        default: \"value\"\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/OpenCode/OpenCodeUsageSnapshot.swift",
    "content": "import Foundation\n\npublic struct OpenCodeUsageSnapshot: Sendable {\n    public let rollingUsagePercent: Double\n    public let weeklyUsagePercent: Double\n    public let rollingResetInSec: Int\n    public let weeklyResetInSec: Int\n    public let updatedAt: Date\n\n    public init(\n        rollingUsagePercent: Double,\n        weeklyUsagePercent: Double,\n        rollingResetInSec: Int,\n        weeklyResetInSec: Int,\n        updatedAt: Date)\n    {\n        self.rollingUsagePercent = rollingUsagePercent\n        self.weeklyUsagePercent = weeklyUsagePercent\n        self.rollingResetInSec = rollingResetInSec\n        self.weeklyResetInSec = weeklyResetInSec\n        self.updatedAt = updatedAt\n    }\n\n    public func toUsageSnapshot() -> UsageSnapshot {\n        let rollingReset = self.updatedAt.addingTimeInterval(TimeInterval(self.rollingResetInSec))\n        let weeklyReset = self.updatedAt.addingTimeInterval(TimeInterval(self.weeklyResetInSec))\n\n        let primary = RateWindow(\n            usedPercent: self.rollingUsagePercent,\n            windowMinutes: 5 * 60,\n            resetsAt: rollingReset,\n            resetDescription: nil)\n        let secondary = RateWindow(\n            usedPercent: self.weeklyUsagePercent,\n            windowMinutes: 7 * 24 * 60,\n            resetsAt: weeklyReset,\n            resetDescription: nil)\n\n        return UsageSnapshot(\n            primary: primary,\n            secondary: secondary,\n            updatedAt: self.updatedAt,\n            identity: nil)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/OpenRouter/OpenRouterProviderDescriptor.swift",
    "content": "import CodexBarMacroSupport\nimport Foundation\n\n@ProviderDescriptorRegistration\n@ProviderDescriptorDefinition\npublic enum OpenRouterProviderDescriptor {\n    static func makeDescriptor() -> ProviderDescriptor {\n        ProviderDescriptor(\n            id: .openrouter,\n            metadata: ProviderMetadata(\n                id: .openrouter,\n                displayName: \"OpenRouter\",\n                sessionLabel: \"Credits\",\n                weeklyLabel: \"Usage\",\n                opusLabel: nil,\n                supportsOpus: false,\n                supportsCredits: true,\n                creditsHint: \"Credit balance from OpenRouter API\",\n                toggleTitle: \"Show OpenRouter usage\",\n                cliName: \"openrouter\",\n                defaultEnabled: false,\n                isPrimaryProvider: false,\n                usesAccountFallback: false,\n                dashboardURL: \"https://openrouter.ai/settings/credits\",\n                statusPageURL: nil,\n                statusLinkURL: \"https://status.openrouter.ai\"),\n            branding: ProviderBranding(\n                iconStyle: .openrouter,\n                iconResourceName: \"ProviderIcon-openrouter\",\n                color: ProviderColor(red: 100 / 255, green: 103 / 255, blue: 242 / 255)),\n            tokenCost: ProviderTokenCostConfig(\n                supportsTokenCost: false,\n                noDataMessage: { \"OpenRouter cost summary is not yet supported.\" }),\n            fetchPlan: ProviderFetchPlan(\n                sourceModes: [.auto, .api],\n                pipeline: ProviderFetchPipeline(resolveStrategies: { _ in [OpenRouterAPIFetchStrategy()] })),\n            cli: ProviderCLIConfig(\n                name: \"openrouter\",\n                aliases: [\"or\"],\n                versionDetector: nil))\n    }\n}\n\nstruct OpenRouterAPIFetchStrategy: ProviderFetchStrategy {\n    let id: String = \"openrouter.api\"\n    let kind: ProviderFetchKind = .apiToken\n\n    func isAvailable(_ context: ProviderFetchContext) async -> Bool {\n        Self.resolveToken(environment: context.env) != nil\n    }\n\n    func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {\n        guard let apiKey = Self.resolveToken(environment: context.env) else {\n            throw OpenRouterSettingsError.missingToken\n        }\n        let usage = try await OpenRouterUsageFetcher.fetchUsage(\n            apiKey: apiKey,\n            environment: context.env)\n        return self.makeResult(\n            usage: usage.toUsageSnapshot(),\n            sourceLabel: \"api\")\n    }\n\n    func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool {\n        false\n    }\n\n    private static func resolveToken(environment: [String: String]) -> String? {\n        ProviderTokenResolver.openRouterToken(environment: environment)\n    }\n}\n\n/// Errors related to OpenRouter settings\npublic enum OpenRouterSettingsError: LocalizedError, Sendable {\n    case missingToken\n\n    public var errorDescription: String? {\n        switch self {\n        case .missingToken:\n            \"OpenRouter API token not configured. Set OPENROUTER_API_KEY environment variable or configure in Settings.\"\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/OpenRouter/OpenRouterSettingsReader.swift",
    "content": "import Foundation\n\n/// Reads OpenRouter settings from environment variables\npublic enum OpenRouterSettingsReader {\n    /// Environment variable key for OpenRouter API token\n    public static let envKey = \"OPENROUTER_API_KEY\"\n\n    /// Returns the API token from environment if present and non-empty\n    public static func apiToken(environment: [String: String] = ProcessInfo.processInfo.environment) -> String? {\n        self.cleaned(environment[self.envKey])\n    }\n\n    /// Returns the API URL, defaulting to production endpoint\n    public static func apiURL(environment: [String: String] = ProcessInfo.processInfo.environment) -> URL {\n        if let override = environment[\"OPENROUTER_API_URL\"],\n           let url = URL(string: cleaned(override) ?? \"\")\n        {\n            return url\n        }\n        return URL(string: \"https://openrouter.ai/api/v1\")!\n    }\n\n    static func cleaned(_ raw: String?) -> String? {\n        guard var value = raw?.trimmingCharacters(in: .whitespacesAndNewlines), !value.isEmpty else {\n            return nil\n        }\n\n        if (value.hasPrefix(\"\\\"\") && value.hasSuffix(\"\\\"\")) ||\n            (value.hasPrefix(\"'\") && value.hasSuffix(\"'\"))\n        {\n            value.removeFirst()\n            value.removeLast()\n        }\n\n        value = value.trimmingCharacters(in: .whitespacesAndNewlines)\n        return value.isEmpty ? nil : value\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/OpenRouter/OpenRouterUsageStats.swift",
    "content": "import Foundation\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n\n/// OpenRouter credits API response\npublic struct OpenRouterCreditsResponse: Decodable, Sendable {\n    public let data: OpenRouterCreditsData\n}\n\n/// OpenRouter credits data\npublic struct OpenRouterCreditsData: Decodable, Sendable {\n    /// Total credits ever added to the account (in USD)\n    public let totalCredits: Double\n    /// Total credits used (in USD)\n    public let totalUsage: Double\n\n    private enum CodingKeys: String, CodingKey {\n        case totalCredits = \"total_credits\"\n        case totalUsage = \"total_usage\"\n    }\n\n    /// Remaining credits (total - usage)\n    public var balance: Double {\n        max(0, self.totalCredits - self.totalUsage)\n    }\n\n    /// Usage percentage (0-100)\n    public var usedPercent: Double {\n        guard self.totalCredits > 0 else { return 0 }\n        return min(100, (self.totalUsage / self.totalCredits) * 100)\n    }\n}\n\n/// OpenRouter key info API response\npublic struct OpenRouterKeyResponse: Decodable, Sendable {\n    public let data: OpenRouterKeyData\n}\n\n/// OpenRouter key data with quota and rate limit info\npublic struct OpenRouterKeyData: Decodable, Sendable {\n    /// Rate limit per interval\n    public let rateLimit: OpenRouterRateLimit?\n    /// Usage limits\n    public let limit: Double?\n    /// Current usage\n    public let usage: Double?\n\n    private enum CodingKeys: String, CodingKey {\n        case rateLimit = \"rate_limit\"\n        case limit\n        case usage\n    }\n}\n\n/// OpenRouter rate limit info\npublic struct OpenRouterRateLimit: Codable, Sendable {\n    /// Number of requests allowed\n    public let requests: Int\n    /// Interval for the rate limit (e.g., \"10s\", \"1m\")\n    public let interval: String\n}\n\npublic enum OpenRouterKeyQuotaStatus: String, Codable, Sendable {\n    case available\n    case noLimitConfigured\n    case unavailable\n}\n\n/// Complete OpenRouter usage snapshot\npublic struct OpenRouterUsageSnapshot: Codable, Sendable {\n    public let totalCredits: Double\n    public let totalUsage: Double\n    public let balance: Double\n    public let usedPercent: Double\n    public let keyDataFetched: Bool\n    public let keyLimit: Double?\n    public let keyUsage: Double?\n    public let rateLimit: OpenRouterRateLimit?\n    public let updatedAt: Date\n\n    public init(\n        totalCredits: Double,\n        totalUsage: Double,\n        balance: Double,\n        usedPercent: Double,\n        keyDataFetched: Bool = false,\n        keyLimit: Double? = nil,\n        keyUsage: Double? = nil,\n        rateLimit: OpenRouterRateLimit?,\n        updatedAt: Date)\n    {\n        self.totalCredits = totalCredits\n        self.totalUsage = totalUsage\n        self.balance = balance\n        self.usedPercent = usedPercent\n        self.keyDataFetched = keyDataFetched || keyLimit != nil || keyUsage != nil\n        self.keyLimit = keyLimit\n        self.keyUsage = keyUsage\n        self.rateLimit = rateLimit\n        self.updatedAt = updatedAt\n    }\n\n    /// Returns true if this snapshot contains valid data\n    public var isValid: Bool {\n        self.totalCredits >= 0\n    }\n\n    public var hasValidKeyQuota: Bool {\n        guard self.keyDataFetched,\n              let keyLimit,\n              let keyUsage\n        else {\n            return false\n        }\n        return keyLimit > 0 && keyUsage >= 0\n    }\n\n    public var keyQuotaStatus: OpenRouterKeyQuotaStatus {\n        if self.hasValidKeyQuota {\n            return .available\n        }\n        guard self.keyDataFetched else {\n            return .unavailable\n        }\n        if let keyLimit, keyLimit > 0 {\n            return .unavailable\n        }\n        return .noLimitConfigured\n    }\n\n    public var keyRemaining: Double? {\n        guard self.hasValidKeyQuota,\n              let keyLimit,\n              let keyUsage\n        else {\n            return nil\n        }\n        return max(0, keyLimit - keyUsage)\n    }\n\n    public var keyUsedPercent: Double? {\n        guard self.hasValidKeyQuota,\n              let keyLimit,\n              let keyUsage\n        else {\n            return nil\n        }\n        return min(100, max(0, (keyUsage / keyLimit) * 100))\n    }\n}\n\nextension OpenRouterUsageSnapshot {\n    public func toUsageSnapshot() -> UsageSnapshot {\n        let primary: RateWindow? = if let keyUsedPercent {\n            RateWindow(\n                usedPercent: keyUsedPercent,\n                windowMinutes: nil,\n                resetsAt: nil,\n                resetDescription: nil)\n        } else {\n            nil\n        }\n\n        // Format balance for identity display\n        let balanceStr = String(format: \"$%.2f\", balance)\n        let identity = ProviderIdentitySnapshot(\n            providerID: .openrouter,\n            accountEmail: nil,\n            accountOrganization: nil,\n            loginMethod: \"Balance: \\(balanceStr)\")\n\n        return UsageSnapshot(\n            primary: primary,\n            secondary: nil,\n            tertiary: nil,\n            providerCost: nil,\n            openRouterUsage: self,\n            updatedAt: self.updatedAt,\n            identity: identity)\n    }\n}\n\n/// Fetches usage stats from the OpenRouter API\npublic struct OpenRouterUsageFetcher: Sendable {\n    private static let log = CodexBarLog.logger(LogCategories.openRouterUsage)\n    private static let rateLimitTimeoutSeconds: TimeInterval = 1.0\n    private static let creditsRequestTimeoutSeconds: TimeInterval = 15\n    private static let maxErrorBodyLength = 240\n    private static let maxDebugErrorBodyLength = 2000\n    private static let debugFullErrorBodiesEnvKey = \"CODEXBAR_DEBUG_OPENROUTER_ERROR_BODIES\"\n    private static let httpRefererEnvKey = \"OPENROUTER_HTTP_REFERER\"\n    private static let clientTitleEnvKey = \"OPENROUTER_X_TITLE\"\n    private static let defaultClientTitle = \"CodexBar\"\n\n    /// Fetches credits usage from OpenRouter using the provided API key\n    public static func fetchUsage(\n        apiKey: String,\n        environment: [String: String] = ProcessInfo.processInfo.environment) async throws -> OpenRouterUsageSnapshot\n    {\n        guard !apiKey.isEmpty else {\n            throw OpenRouterUsageError.invalidCredentials\n        }\n\n        let baseURL = OpenRouterSettingsReader.apiURL(environment: environment)\n        let creditsURL = baseURL.appendingPathComponent(\"credits\")\n\n        var request = URLRequest(url: creditsURL)\n        request.httpMethod = \"GET\"\n        request.setValue(\"Bearer \\(apiKey)\", forHTTPHeaderField: \"Authorization\")\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Accept\")\n        request.timeoutInterval = Self.creditsRequestTimeoutSeconds\n        if let referer = Self.sanitizedHeaderValue(environment[self.httpRefererEnvKey]) {\n            request.setValue(referer, forHTTPHeaderField: \"HTTP-Referer\")\n        }\n        let title = Self.sanitizedHeaderValue(environment[self.clientTitleEnvKey]) ?? Self.defaultClientTitle\n        request.setValue(title, forHTTPHeaderField: \"X-Title\")\n\n        let (data, response) = try await URLSession.shared.data(for: request)\n\n        guard let httpResponse = response as? HTTPURLResponse else {\n            throw OpenRouterUsageError.networkError(\"Invalid response\")\n        }\n\n        guard httpResponse.statusCode == 200 else {\n            let errorSummary = LogRedactor.redact(Self.sanitizedResponseBodySummary(data))\n            if Self.debugFullErrorBodiesEnabled(environment: environment),\n               let debugBody = Self.redactedDebugResponseBody(data)\n            {\n                Self.log.debug(\"OpenRouter non-200 body (redacted): \\(LogRedactor.redact(debugBody))\")\n            }\n            Self.log.error(\"OpenRouter API returned \\(httpResponse.statusCode): \\(errorSummary)\")\n            throw OpenRouterUsageError.apiError(\"HTTP \\(httpResponse.statusCode)\")\n        }\n\n        do {\n            let decoder = JSONDecoder()\n            let creditsResponse = try decoder.decode(OpenRouterCreditsResponse.self, from: data)\n\n            // Optionally fetch key quota/rate-limit info from /key endpoint, but keep this bounded so\n            // credits updates are not blocked by a slow or unavailable secondary endpoint.\n            let keyFetch = await fetchKeyData(\n                apiKey: apiKey,\n                baseURL: baseURL,\n                timeoutSeconds: Self.rateLimitTimeoutSeconds)\n\n            return OpenRouterUsageSnapshot(\n                totalCredits: creditsResponse.data.totalCredits,\n                totalUsage: creditsResponse.data.totalUsage,\n                balance: creditsResponse.data.balance,\n                usedPercent: creditsResponse.data.usedPercent,\n                keyDataFetched: keyFetch.fetched,\n                keyLimit: keyFetch.data?.limit,\n                keyUsage: keyFetch.data?.usage,\n                rateLimit: keyFetch.data?.rateLimit,\n                updatedAt: Date())\n        } catch let error as DecodingError {\n            Self.log.error(\"OpenRouter JSON decoding error: \\(error.localizedDescription)\")\n            throw OpenRouterUsageError.parseFailed(error.localizedDescription)\n        } catch let error as OpenRouterUsageError {\n            throw error\n        } catch {\n            Self.log.error(\"OpenRouter parsing error: \\(error.localizedDescription)\")\n            throw OpenRouterUsageError.parseFailed(error.localizedDescription)\n        }\n    }\n\n    /// Fetches key quota/rate-limit info from /key endpoint\n    private struct OpenRouterKeyFetchResult {\n        let data: OpenRouterKeyData?\n        let fetched: Bool\n    }\n\n    private static func fetchKeyData(\n        apiKey: String,\n        baseURL: URL,\n        timeoutSeconds: TimeInterval) async -> OpenRouterKeyFetchResult\n    {\n        let timeout = max(0.1, timeoutSeconds)\n        let timeoutNanoseconds = UInt64(timeout * 1_000_000_000)\n\n        return await withTaskGroup(of: OpenRouterKeyFetchResult.self) { group in\n            group.addTask {\n                await Self.fetchKeyDataRequest(\n                    apiKey: apiKey,\n                    baseURL: baseURL,\n                    timeoutSeconds: timeout)\n            }\n            group.addTask {\n                do {\n                    try await Task.sleep(nanoseconds: timeoutNanoseconds)\n                } catch {\n                    // Cancelled because the /key request finished first.\n                    return OpenRouterKeyFetchResult(data: nil, fetched: false)\n                }\n                guard !Task.isCancelled else {\n                    return OpenRouterKeyFetchResult(data: nil, fetched: false)\n                }\n                Self.log.debug(\"OpenRouter /key enrichment timed out after \\(timeout)s\")\n                return OpenRouterKeyFetchResult(data: nil, fetched: false)\n            }\n\n            let result = await group.next()\n            group.cancelAll()\n            if let result {\n                return result\n            }\n            return OpenRouterKeyFetchResult(data: nil, fetched: false)\n        }\n    }\n\n    private static func fetchKeyDataRequest(\n        apiKey: String,\n        baseURL: URL,\n        timeoutSeconds: TimeInterval) async -> OpenRouterKeyFetchResult\n    {\n        let keyURL = baseURL.appendingPathComponent(\"key\")\n\n        var request = URLRequest(url: keyURL)\n        request.httpMethod = \"GET\"\n        request.setValue(\"Bearer \\(apiKey)\", forHTTPHeaderField: \"Authorization\")\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Accept\")\n        request.timeoutInterval = timeoutSeconds\n\n        do {\n            let (data, response) = try await URLSession.shared.data(for: request)\n\n            guard let httpResponse = response as? HTTPURLResponse,\n                  httpResponse.statusCode == 200\n            else {\n                return OpenRouterKeyFetchResult(data: nil, fetched: false)\n            }\n\n            let decoder = JSONDecoder()\n            let keyResponse = try decoder.decode(OpenRouterKeyResponse.self, from: data)\n            return OpenRouterKeyFetchResult(data: keyResponse.data, fetched: true)\n        } catch {\n            Self.log.debug(\"Failed to fetch OpenRouter /key enrichment: \\(error.localizedDescription)\")\n            return OpenRouterKeyFetchResult(data: nil, fetched: false)\n        }\n    }\n\n    private static func debugFullErrorBodiesEnabled(environment: [String: String]) -> Bool {\n        environment[self.debugFullErrorBodiesEnvKey] == \"1\"\n    }\n\n    private static func sanitizedHeaderValue(_ raw: String?) -> String? {\n        OpenRouterSettingsReader.cleaned(raw)\n    }\n\n    private static func sanitizedResponseBodySummary(_ data: Data) -> String {\n        guard !data.isEmpty else { return \"empty body\" }\n\n        guard let rawBody = String(bytes: data, encoding: .utf8) else {\n            return \"non-text body (\\(data.count) bytes)\"\n        }\n\n        let body = Self.redactSensitiveBodyContent(rawBody)\n            .replacingOccurrences(of: #\"\\s+\"#, with: \" \", options: .regularExpression)\n            .trimmingCharacters(in: .whitespacesAndNewlines)\n\n        guard !body.isEmpty else { return \"non-text body (\\(data.count) bytes)\" }\n        guard body.count > Self.maxErrorBodyLength else { return body }\n\n        let index = body.index(body.startIndex, offsetBy: Self.maxErrorBodyLength)\n        return \"\\(body[..<index])… [truncated]\"\n    }\n\n    private static func redactedDebugResponseBody(_ data: Data) -> String? {\n        guard let rawBody = String(bytes: data, encoding: .utf8) else { return nil }\n\n        let body = Self.redactSensitiveBodyContent(rawBody)\n            .replacingOccurrences(of: #\"\\s+\"#, with: \" \", options: .regularExpression)\n            .trimmingCharacters(in: .whitespacesAndNewlines)\n        guard !body.isEmpty else { return nil }\n        guard body.count > Self.maxDebugErrorBodyLength else { return body }\n\n        let index = body.index(body.startIndex, offsetBy: Self.maxDebugErrorBodyLength)\n        return \"\\(body[..<index])… [truncated]\"\n    }\n\n    private static func redactSensitiveBodyContent(_ text: String) -> String {\n        let replacements: [(String, String)] = [\n            (#\"(?i)(bearer\\s+)[A-Za-z0-9._\\-]+\"#, \"$1[REDACTED]\"),\n            (#\"(?i)(sk-or-v1-)[A-Za-z0-9._\\-]+\"#, \"$1[REDACTED]\"),\n            (\n                #\"(?i)(\\\"(?:api_?key|authorization|token|access_token|refresh_token)\\\"\\s*:\\s*\\\")([^\\\"]+)(\\\")\"#,\n                \"$1[REDACTED]$3\"),\n            (\n                #\"(?i)((?:api_?key|authorization|token|access_token|refresh_token)\\s*[=:]\\s*)([^,\\s]+)\"#,\n                \"$1[REDACTED]\"),\n        ]\n\n        return replacements.reduce(text) { partial, replacement in\n            partial.replacingOccurrences(\n                of: replacement.0,\n                with: replacement.1,\n                options: .regularExpression)\n        }\n    }\n\n    #if DEBUG\n    static func _sanitizedResponseBodySummaryForTesting(_ body: String) -> String {\n        self.sanitizedResponseBodySummary(Data(body.utf8))\n    }\n\n    static func _redactedDebugResponseBodyForTesting(_ body: String) -> String? {\n        self.redactedDebugResponseBody(Data(body.utf8))\n    }\n    #endif\n}\n\n/// Errors that can occur during OpenRouter usage fetching\npublic enum OpenRouterUsageError: LocalizedError, Sendable {\n    case invalidCredentials\n    case networkError(String)\n    case apiError(String)\n    case parseFailed(String)\n\n    public var errorDescription: String? {\n        switch self {\n        case .invalidCredentials:\n            \"Invalid OpenRouter API credentials\"\n        case let .networkError(message):\n            \"OpenRouter network error: \\(message)\"\n        case let .apiError(message):\n            \"OpenRouter API error: \\(message)\"\n        case let .parseFailed(message):\n            \"Failed to parse OpenRouter response: \\(message)\"\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/ProviderBranding.swift",
    "content": "import Foundation\n\npublic struct ProviderColor: Sendable, Equatable {\n    public let red: Double\n    public let green: Double\n    public let blue: Double\n\n    public init(red: Double, green: Double, blue: Double) {\n        self.red = red\n        self.green = green\n        self.blue = blue\n    }\n}\n\npublic struct ProviderBranding: Sendable {\n    public let iconStyle: IconStyle\n    public let iconResourceName: String\n    public let color: ProviderColor\n\n    public init(iconStyle: IconStyle, iconResourceName: String, color: ProviderColor) {\n        self.iconStyle = iconStyle\n        self.iconResourceName = iconResourceName\n        self.color = color\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/ProviderCLIConfig.swift",
    "content": "import Foundation\n\npublic struct ProviderCLIConfig: Sendable {\n    public let name: String\n    public let aliases: [String]\n    public let versionDetector: (@Sendable (BrowserDetection) -> String?)?\n\n    public init(\n        name: String,\n        aliases: [String] = [],\n        versionDetector: (@Sendable (BrowserDetection) -> String?)?)\n    {\n        self.name = name\n        self.aliases = aliases\n        self.versionDetector = versionDetector\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/ProviderCandidateRetryRunner.swift",
    "content": "import Foundation\n\nenum ProviderCandidateRetryRunnerError: Error {\n    case noCandidates\n}\n\nenum ProviderCandidateRetryRunner {\n    static func run<Candidate, Output>(\n        _ candidates: [Candidate],\n        shouldRetry: (Error) -> Bool,\n        onRetry: (Candidate, Error) -> Void = { _, _ in },\n        attempt: (Candidate) async throws -> Output) async throws -> Output\n    {\n        guard !candidates.isEmpty else {\n            throw ProviderCandidateRetryRunnerError.noCandidates\n        }\n\n        for (index, candidate) in candidates.enumerated() {\n            do {\n                return try await attempt(candidate)\n            } catch {\n                let hasMoreCandidates = index + 1 < candidates.count\n                guard hasMoreCandidates, shouldRetry(error) else {\n                    throw error\n                }\n                onRetry(candidate, error)\n            }\n        }\n        throw ProviderCandidateRetryRunnerError.noCandidates\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/ProviderCookieSource.swift",
    "content": "import Foundation\n\npublic enum ProviderCookieSource: String, CaseIterable, Identifiable, Sendable, Codable {\n    case auto\n    case manual\n    case off\n\n    public var id: String {\n        self.rawValue\n    }\n\n    public var displayName: String {\n        switch self {\n        case .auto: \"Auto\"\n        case .manual: \"Manual\"\n        case .off: \"Off\"\n        }\n    }\n\n    public var isEnabled: Bool {\n        switch self {\n        case .off: false\n        case .auto, .manual: true\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/ProviderDescriptor.swift",
    "content": "import Foundation\n\npublic struct ProviderTokenCostConfig: Sendable {\n    public let supportsTokenCost: Bool\n    public let noDataMessage: @Sendable () -> String\n\n    public init(supportsTokenCost: Bool, noDataMessage: @escaping @Sendable () -> String) {\n        self.supportsTokenCost = supportsTokenCost\n        self.noDataMessage = noDataMessage\n    }\n}\n\npublic struct ProviderDescriptor: Sendable {\n    public let id: UsageProvider\n    public let metadata: ProviderMetadata\n    public let branding: ProviderBranding\n    public let tokenCost: ProviderTokenCostConfig\n    public let fetchPlan: ProviderFetchPlan\n    public let cli: ProviderCLIConfig\n\n    public init(\n        id: UsageProvider,\n        metadata: ProviderMetadata,\n        branding: ProviderBranding,\n        tokenCost: ProviderTokenCostConfig,\n        fetchPlan: ProviderFetchPlan,\n        cli: ProviderCLIConfig)\n    {\n        self.id = id\n        self.metadata = metadata\n        self.branding = branding\n        self.tokenCost = tokenCost\n        self.fetchPlan = fetchPlan\n        self.cli = cli\n    }\n\n    public func fetchOutcome(context: ProviderFetchContext) async -> ProviderFetchOutcome {\n        await self.fetchPlan.fetchOutcome(context: context, provider: self.id)\n    }\n\n    public func fetch(context: ProviderFetchContext) async throws -> ProviderFetchResult {\n        let outcome = await self.fetchOutcome(context: context)\n        return try outcome.result.get()\n    }\n}\n\npublic enum ProviderDescriptorRegistry {\n    private final class Store: @unchecked Sendable {\n        var ordered: [ProviderDescriptor] = []\n        var byID: [UsageProvider: ProviderDescriptor] = [:]\n    }\n\n    private static let lock = NSLock()\n    private static let store = Store()\n    private static let descriptorsByID: [UsageProvider: ProviderDescriptor] = [\n        .codex: CodexProviderDescriptor.descriptor,\n        .claude: ClaudeProviderDescriptor.descriptor,\n        .cursor: CursorProviderDescriptor.descriptor,\n        .opencode: OpenCodeProviderDescriptor.descriptor,\n        .alibaba: AlibabaCodingPlanProviderDescriptor.descriptor,\n        .factory: FactoryProviderDescriptor.descriptor,\n        .gemini: GeminiProviderDescriptor.descriptor,\n        .antigravity: AntigravityProviderDescriptor.descriptor,\n        .copilot: CopilotProviderDescriptor.descriptor,\n        .zai: ZaiProviderDescriptor.descriptor,\n        .minimax: MiniMaxProviderDescriptor.descriptor,\n        .kimi: KimiProviderDescriptor.descriptor,\n        .kilo: KiloProviderDescriptor.descriptor,\n        .kiro: KiroProviderDescriptor.descriptor,\n        .vertexai: VertexAIProviderDescriptor.descriptor,\n        .augment: AugmentProviderDescriptor.descriptor,\n        .jetbrains: JetBrainsProviderDescriptor.descriptor,\n        .kimik2: KimiK2ProviderDescriptor.descriptor,\n        .amp: AmpProviderDescriptor.descriptor,\n        .ollama: OllamaProviderDescriptor.descriptor,\n        .synthetic: SyntheticProviderDescriptor.descriptor,\n        .openrouter: OpenRouterProviderDescriptor.descriptor,\n        .warp: WarpProviderDescriptor.descriptor,\n    ]\n    private static let bootstrap: Void = {\n        for provider in UsageProvider.allCases {\n            guard let descriptor = descriptorsByID[provider] else {\n                preconditionFailure(\"Missing ProviderDescriptor for \\(provider.rawValue)\")\n            }\n            _ = ProviderDescriptorRegistry.register(descriptor)\n        }\n    }()\n\n    private static func ensureBootstrapped() {\n        _ = self.bootstrap\n    }\n\n    @discardableResult\n    public static func register(_ descriptor: ProviderDescriptor) -> ProviderDescriptor {\n        self.lock.lock()\n        defer { self.lock.unlock() }\n        if self.store.byID[descriptor.id] == nil {\n            self.store.ordered.append(descriptor)\n        }\n        self.store.byID[descriptor.id] = descriptor\n        return descriptor\n    }\n\n    public static var all: [ProviderDescriptor] {\n        self.ensureBootstrapped()\n        self.lock.lock()\n        defer { self.lock.unlock() }\n        return self.store.ordered\n    }\n\n    public static var metadata: [UsageProvider: ProviderMetadata] {\n        Dictionary(uniqueKeysWithValues: self.all.map { ($0.id, $0.metadata) })\n    }\n\n    public static func descriptor(for id: UsageProvider) -> ProviderDescriptor {\n        self.ensureBootstrapped()\n        if let found = self.store.byID[id] { return found }\n        if let found = self.all.first(where: { $0.id == id }) { return found }\n        fatalError(\"Missing ProviderDescriptor for \\(id.rawValue)\")\n    }\n\n    public static var cliNameMap: [String: UsageProvider] {\n        self.ensureBootstrapped()\n        var map: [String: UsageProvider] = [:]\n        for descriptor in self.all {\n            map[descriptor.cli.name] = descriptor.id\n            for alias in descriptor.cli.aliases {\n                map[alias] = descriptor.id\n            }\n        }\n        return map\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/ProviderFetchPlan.swift",
    "content": "import Foundation\n\npublic enum ProviderRuntime: Sendable {\n    case app\n    case cli\n}\n\npublic enum ProviderSourceMode: String, CaseIterable, Sendable, Codable {\n    case auto\n    case web\n    case cli\n    case oauth\n    case api\n\n    public var usesWeb: Bool {\n        self == .auto || self == .web\n    }\n}\n\npublic struct ProviderFetchContext: Sendable {\n    public let runtime: ProviderRuntime\n    public let sourceMode: ProviderSourceMode\n    public let includeCredits: Bool\n    public let webTimeout: TimeInterval\n    public let webDebugDumpHTML: Bool\n    public let verbose: Bool\n    public let env: [String: String]\n    public let settings: ProviderSettingsSnapshot?\n    public let fetcher: UsageFetcher\n    public let claudeFetcher: any ClaudeUsageFetching\n    public let browserDetection: BrowserDetection\n\n    public init(\n        runtime: ProviderRuntime,\n        sourceMode: ProviderSourceMode,\n        includeCredits: Bool,\n        webTimeout: TimeInterval,\n        webDebugDumpHTML: Bool,\n        verbose: Bool,\n        env: [String: String],\n        settings: ProviderSettingsSnapshot?,\n        fetcher: UsageFetcher,\n        claudeFetcher: any ClaudeUsageFetching,\n        browserDetection: BrowserDetection)\n    {\n        self.runtime = runtime\n        self.sourceMode = sourceMode\n        self.includeCredits = includeCredits\n        self.webTimeout = webTimeout\n        self.webDebugDumpHTML = webDebugDumpHTML\n        self.verbose = verbose\n        self.env = env\n        self.settings = settings\n        self.fetcher = fetcher\n        self.claudeFetcher = claudeFetcher\n        self.browserDetection = browserDetection\n    }\n}\n\npublic struct ProviderFetchResult: Sendable {\n    public let usage: UsageSnapshot\n    public let credits: CreditsSnapshot?\n    public let dashboard: OpenAIDashboardSnapshot?\n    public let sourceLabel: String\n    public let strategyID: String\n    public let strategyKind: ProviderFetchKind\n\n    public init(\n        usage: UsageSnapshot,\n        credits: CreditsSnapshot?,\n        dashboard: OpenAIDashboardSnapshot?,\n        sourceLabel: String,\n        strategyID: String,\n        strategyKind: ProviderFetchKind)\n    {\n        self.usage = usage\n        self.credits = credits\n        self.dashboard = dashboard\n        self.sourceLabel = sourceLabel\n        self.strategyID = strategyID\n        self.strategyKind = strategyKind\n    }\n}\n\npublic struct ProviderFetchAttempt: Sendable {\n    public let strategyID: String\n    public let kind: ProviderFetchKind\n    public let wasAvailable: Bool\n    public let errorDescription: String?\n\n    public init(strategyID: String, kind: ProviderFetchKind, wasAvailable: Bool, errorDescription: String?) {\n        self.strategyID = strategyID\n        self.kind = kind\n        self.wasAvailable = wasAvailable\n        self.errorDescription = errorDescription\n    }\n}\n\npublic struct ProviderFetchOutcome: @unchecked Sendable {\n    public let result: Result<ProviderFetchResult, Error>\n    public let attempts: [ProviderFetchAttempt]\n\n    public init(result: Result<ProviderFetchResult, Error>, attempts: [ProviderFetchAttempt]) {\n        self.result = result\n        self.attempts = attempts\n    }\n}\n\npublic enum ProviderFetchError: LocalizedError, Sendable {\n    case noAvailableStrategy(UsageProvider)\n\n    public var errorDescription: String? {\n        switch self {\n        case let .noAvailableStrategy(provider):\n            \"No available fetch strategy for \\(provider.rawValue).\"\n        }\n    }\n}\n\npublic enum ProviderFetchKind: Sendable {\n    case cli\n    case web\n    case oauth\n    case apiToken\n    case localProbe\n    case webDashboard\n}\n\npublic protocol ProviderFetchStrategy: Sendable {\n    var id: String { get }\n    var kind: ProviderFetchKind { get }\n    func isAvailable(_ context: ProviderFetchContext) async -> Bool\n    func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult\n    func shouldFallback(on error: Error, context: ProviderFetchContext) -> Bool\n}\n\nextension ProviderFetchStrategy {\n    public func makeResult(\n        usage: UsageSnapshot,\n        credits: CreditsSnapshot? = nil,\n        dashboard: OpenAIDashboardSnapshot? = nil,\n        sourceLabel: String) -> ProviderFetchResult\n    {\n        ProviderFetchResult(\n            usage: usage,\n            credits: credits,\n            dashboard: dashboard,\n            sourceLabel: sourceLabel,\n            strategyID: self.id,\n            strategyKind: self.kind)\n    }\n}\n\npublic struct ProviderFetchPipeline: Sendable {\n    public let resolveStrategies: @Sendable (ProviderFetchContext) async -> [any ProviderFetchStrategy]\n\n    public init(resolveStrategies: @escaping @Sendable (ProviderFetchContext) async -> [any ProviderFetchStrategy]) {\n        self.resolveStrategies = resolveStrategies\n    }\n\n    public func fetch(context: ProviderFetchContext, provider: UsageProvider) async -> ProviderFetchOutcome {\n        let strategies = await self.resolveStrategies(context)\n        var attempts: [ProviderFetchAttempt] = []\n        attempts.reserveCapacity(strategies.count)\n        var lastAvailableError: Error?\n\n        for strategy in strategies {\n            let available = await strategy.isAvailable(context)\n\n            guard available else {\n                attempts.append(ProviderFetchAttempt(\n                    strategyID: strategy.id,\n                    kind: strategy.kind,\n                    wasAvailable: false,\n                    errorDescription: nil))\n                continue\n            }\n\n            do {\n                let result = try await strategy.fetch(context)\n                attempts.append(ProviderFetchAttempt(\n                    strategyID: strategy.id,\n                    kind: strategy.kind,\n                    wasAvailable: true,\n                    errorDescription: nil))\n                return ProviderFetchOutcome(result: .success(result), attempts: attempts)\n            } catch {\n                lastAvailableError = error\n                attempts.append(ProviderFetchAttempt(\n                    strategyID: strategy.id,\n                    kind: strategy.kind,\n                    wasAvailable: true,\n                    errorDescription: error.localizedDescription))\n                if strategy.shouldFallback(on: error, context: context) {\n                    continue\n                }\n                return ProviderFetchOutcome(result: .failure(error), attempts: attempts)\n            }\n        }\n\n        let error = lastAvailableError ?? ProviderFetchError.noAvailableStrategy(provider)\n        return ProviderFetchOutcome(result: .failure(error), attempts: attempts)\n    }\n}\n\npublic struct ProviderFetchPlan: Sendable {\n    public let sourceModes: Set<ProviderSourceMode>\n    public let pipeline: ProviderFetchPipeline\n\n    public init(sourceModes: Set<ProviderSourceMode>, pipeline: ProviderFetchPipeline) {\n        self.sourceModes = sourceModes\n        self.pipeline = pipeline\n    }\n\n    public func fetchOutcome(context: ProviderFetchContext, provider: UsageProvider) async -> ProviderFetchOutcome {\n        await self.pipeline.fetch(context: context, provider: provider)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/ProviderInteractionContext.swift",
    "content": "import Foundation\n\npublic enum ProviderInteraction: Sendable, Equatable {\n    case background\n    case userInitiated\n}\n\npublic enum ProviderInteractionContext {\n    @TaskLocal public static var current: ProviderInteraction = .background\n}\n\npublic enum ProviderRefreshPhase: Sendable, Equatable {\n    case regular\n    case startup\n}\n\npublic enum ProviderRefreshContext {\n    @TaskLocal public static var current: ProviderRefreshPhase = .regular\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/ProviderSettingsSnapshot.swift",
    "content": "import Foundation\n\npublic struct ProviderSettingsSnapshot: Sendable {\n    public static func make(\n        debugMenuEnabled: Bool = false,\n        debugKeepCLISessionsAlive: Bool = false,\n        codex: CodexProviderSettings? = nil,\n        claude: ClaudeProviderSettings? = nil,\n        cursor: CursorProviderSettings? = nil,\n        opencode: OpenCodeProviderSettings? = nil,\n        alibaba: AlibabaCodingPlanProviderSettings? = nil,\n        factory: FactoryProviderSettings? = nil,\n        minimax: MiniMaxProviderSettings? = nil,\n        zai: ZaiProviderSettings? = nil,\n        copilot: CopilotProviderSettings? = nil,\n        kilo: KiloProviderSettings? = nil,\n        kimi: KimiProviderSettings? = nil,\n        augment: AugmentProviderSettings? = nil,\n        amp: AmpProviderSettings? = nil,\n        ollama: OllamaProviderSettings? = nil,\n        jetbrains: JetBrainsProviderSettings? = nil) -> ProviderSettingsSnapshot\n    {\n        ProviderSettingsSnapshot(\n            debugMenuEnabled: debugMenuEnabled,\n            debugKeepCLISessionsAlive: debugKeepCLISessionsAlive,\n            codex: codex,\n            claude: claude,\n            cursor: cursor,\n            opencode: opencode,\n            alibaba: alibaba,\n            factory: factory,\n            minimax: minimax,\n            zai: zai,\n            copilot: copilot,\n            kilo: kilo,\n            kimi: kimi,\n            augment: augment,\n            amp: amp,\n            ollama: ollama,\n            jetbrains: jetbrains)\n    }\n\n    public struct CodexProviderSettings: Sendable {\n        public let usageDataSource: CodexUsageDataSource\n        public let cookieSource: ProviderCookieSource\n        public let manualCookieHeader: String?\n\n        public init(\n            usageDataSource: CodexUsageDataSource,\n            cookieSource: ProviderCookieSource,\n            manualCookieHeader: String?)\n        {\n            self.usageDataSource = usageDataSource\n            self.cookieSource = cookieSource\n            self.manualCookieHeader = manualCookieHeader\n        }\n    }\n\n    public struct ClaudeProviderSettings: Sendable {\n        public let usageDataSource: ClaudeUsageDataSource\n        public let webExtrasEnabled: Bool\n        public let cookieSource: ProviderCookieSource\n        public let manualCookieHeader: String?\n\n        public init(\n            usageDataSource: ClaudeUsageDataSource,\n            webExtrasEnabled: Bool,\n            cookieSource: ProviderCookieSource,\n            manualCookieHeader: String?)\n        {\n            self.usageDataSource = usageDataSource\n            self.webExtrasEnabled = webExtrasEnabled\n            self.cookieSource = cookieSource\n            self.manualCookieHeader = manualCookieHeader\n        }\n    }\n\n    public struct CursorProviderSettings: Sendable {\n        public let cookieSource: ProviderCookieSource\n        public let manualCookieHeader: String?\n\n        public init(cookieSource: ProviderCookieSource, manualCookieHeader: String?) {\n            self.cookieSource = cookieSource\n            self.manualCookieHeader = manualCookieHeader\n        }\n    }\n\n    public struct OpenCodeProviderSettings: Sendable {\n        public let cookieSource: ProviderCookieSource\n        public let manualCookieHeader: String?\n        public let workspaceID: String?\n\n        public init(cookieSource: ProviderCookieSource, manualCookieHeader: String?, workspaceID: String?) {\n            self.cookieSource = cookieSource\n            self.manualCookieHeader = manualCookieHeader\n            self.workspaceID = workspaceID\n        }\n    }\n\n    public struct AlibabaCodingPlanProviderSettings: Sendable {\n        public let cookieSource: ProviderCookieSource\n        public let manualCookieHeader: String?\n        public let apiRegion: AlibabaCodingPlanAPIRegion\n\n        public init(\n            cookieSource: ProviderCookieSource = .auto,\n            manualCookieHeader: String? = nil,\n            apiRegion: AlibabaCodingPlanAPIRegion = .international)\n        {\n            self.cookieSource = cookieSource\n            self.manualCookieHeader = manualCookieHeader\n            self.apiRegion = apiRegion\n        }\n    }\n\n    public struct FactoryProviderSettings: Sendable {\n        public let cookieSource: ProviderCookieSource\n        public let manualCookieHeader: String?\n\n        public init(cookieSource: ProviderCookieSource, manualCookieHeader: String?) {\n            self.cookieSource = cookieSource\n            self.manualCookieHeader = manualCookieHeader\n        }\n    }\n\n    public struct MiniMaxProviderSettings: Sendable {\n        public let cookieSource: ProviderCookieSource\n        public let manualCookieHeader: String?\n        public let apiRegion: MiniMaxAPIRegion\n\n        public init(\n            cookieSource: ProviderCookieSource,\n            manualCookieHeader: String?,\n            apiRegion: MiniMaxAPIRegion = .global)\n        {\n            self.cookieSource = cookieSource\n            self.manualCookieHeader = manualCookieHeader\n            self.apiRegion = apiRegion\n        }\n    }\n\n    public struct ZaiProviderSettings: Sendable {\n        public let apiRegion: ZaiAPIRegion\n\n        public init(apiRegion: ZaiAPIRegion = .global) {\n            self.apiRegion = apiRegion\n        }\n    }\n\n    public struct CopilotProviderSettings: Sendable {\n        public init() {}\n    }\n\n    public struct KiloProviderSettings: Sendable {\n        public let usageDataSource: KiloUsageDataSource\n        public let extrasEnabled: Bool\n\n        public init(usageDataSource: KiloUsageDataSource, extrasEnabled: Bool) {\n            self.usageDataSource = usageDataSource\n            self.extrasEnabled = extrasEnabled\n        }\n    }\n\n    public struct KimiProviderSettings: Sendable {\n        public let cookieSource: ProviderCookieSource\n        public let manualCookieHeader: String?\n\n        public init(cookieSource: ProviderCookieSource, manualCookieHeader: String?) {\n            self.cookieSource = cookieSource\n            self.manualCookieHeader = manualCookieHeader\n        }\n    }\n\n    public struct AugmentProviderSettings: Sendable {\n        public let cookieSource: ProviderCookieSource\n        public let manualCookieHeader: String?\n\n        public init(cookieSource: ProviderCookieSource, manualCookieHeader: String?) {\n            self.cookieSource = cookieSource\n            self.manualCookieHeader = manualCookieHeader\n        }\n    }\n\n    public struct JetBrainsProviderSettings: Sendable {\n        public let ideBasePath: String?\n\n        public init(ideBasePath: String?) {\n            self.ideBasePath = ideBasePath\n        }\n    }\n\n    public struct AmpProviderSettings: Sendable {\n        public let cookieSource: ProviderCookieSource\n        public let manualCookieHeader: String?\n\n        public init(cookieSource: ProviderCookieSource, manualCookieHeader: String?) {\n            self.cookieSource = cookieSource\n            self.manualCookieHeader = manualCookieHeader\n        }\n    }\n\n    public struct OllamaProviderSettings: Sendable {\n        public let cookieSource: ProviderCookieSource\n        public let manualCookieHeader: String?\n\n        public init(cookieSource: ProviderCookieSource, manualCookieHeader: String?) {\n            self.cookieSource = cookieSource\n            self.manualCookieHeader = manualCookieHeader\n        }\n    }\n\n    public let debugMenuEnabled: Bool\n    public let debugKeepCLISessionsAlive: Bool\n    public let codex: CodexProviderSettings?\n    public let claude: ClaudeProviderSettings?\n    public let cursor: CursorProviderSettings?\n    public let opencode: OpenCodeProviderSettings?\n    public let alibaba: AlibabaCodingPlanProviderSettings?\n    public let factory: FactoryProviderSettings?\n    public let minimax: MiniMaxProviderSettings?\n    public let zai: ZaiProviderSettings?\n    public let copilot: CopilotProviderSettings?\n    public let kilo: KiloProviderSettings?\n    public let kimi: KimiProviderSettings?\n    public let augment: AugmentProviderSettings?\n    public let amp: AmpProviderSettings?\n    public let ollama: OllamaProviderSettings?\n    public let jetbrains: JetBrainsProviderSettings?\n\n    public var jetbrainsIDEBasePath: String? {\n        self.jetbrains?.ideBasePath\n    }\n\n    public init(\n        debugMenuEnabled: Bool,\n        debugKeepCLISessionsAlive: Bool,\n        codex: CodexProviderSettings?,\n        claude: ClaudeProviderSettings?,\n        cursor: CursorProviderSettings?,\n        opencode: OpenCodeProviderSettings?,\n        alibaba: AlibabaCodingPlanProviderSettings?,\n        factory: FactoryProviderSettings?,\n        minimax: MiniMaxProviderSettings?,\n        zai: ZaiProviderSettings?,\n        copilot: CopilotProviderSettings?,\n        kilo: KiloProviderSettings?,\n        kimi: KimiProviderSettings?,\n        augment: AugmentProviderSettings?,\n        amp: AmpProviderSettings?,\n        ollama: OllamaProviderSettings?,\n        jetbrains: JetBrainsProviderSettings? = nil)\n    {\n        self.debugMenuEnabled = debugMenuEnabled\n        self.debugKeepCLISessionsAlive = debugKeepCLISessionsAlive\n        self.codex = codex\n        self.claude = claude\n        self.cursor = cursor\n        self.opencode = opencode\n        self.alibaba = alibaba\n        self.factory = factory\n        self.minimax = minimax\n        self.zai = zai\n        self.copilot = copilot\n        self.kilo = kilo\n        self.kimi = kimi\n        self.augment = augment\n        self.amp = amp\n        self.ollama = ollama\n        self.jetbrains = jetbrains\n    }\n}\n\npublic enum ProviderSettingsSnapshotContribution: Sendable {\n    case codex(ProviderSettingsSnapshot.CodexProviderSettings)\n    case claude(ProviderSettingsSnapshot.ClaudeProviderSettings)\n    case cursor(ProviderSettingsSnapshot.CursorProviderSettings)\n    case opencode(ProviderSettingsSnapshot.OpenCodeProviderSettings)\n    case alibaba(ProviderSettingsSnapshot.AlibabaCodingPlanProviderSettings)\n    case factory(ProviderSettingsSnapshot.FactoryProviderSettings)\n    case minimax(ProviderSettingsSnapshot.MiniMaxProviderSettings)\n    case zai(ProviderSettingsSnapshot.ZaiProviderSettings)\n    case copilot(ProviderSettingsSnapshot.CopilotProviderSettings)\n    case kilo(ProviderSettingsSnapshot.KiloProviderSettings)\n    case kimi(ProviderSettingsSnapshot.KimiProviderSettings)\n    case augment(ProviderSettingsSnapshot.AugmentProviderSettings)\n    case amp(ProviderSettingsSnapshot.AmpProviderSettings)\n    case ollama(ProviderSettingsSnapshot.OllamaProviderSettings)\n    case jetbrains(ProviderSettingsSnapshot.JetBrainsProviderSettings)\n}\n\npublic struct ProviderSettingsSnapshotBuilder: Sendable {\n    public var debugMenuEnabled: Bool\n    public var debugKeepCLISessionsAlive: Bool\n    public var codex: ProviderSettingsSnapshot.CodexProviderSettings?\n    public var claude: ProviderSettingsSnapshot.ClaudeProviderSettings?\n    public var cursor: ProviderSettingsSnapshot.CursorProviderSettings?\n    public var opencode: ProviderSettingsSnapshot.OpenCodeProviderSettings?\n    public var alibaba: ProviderSettingsSnapshot.AlibabaCodingPlanProviderSettings?\n    public var factory: ProviderSettingsSnapshot.FactoryProviderSettings?\n    public var minimax: ProviderSettingsSnapshot.MiniMaxProviderSettings?\n    public var zai: ProviderSettingsSnapshot.ZaiProviderSettings?\n    public var copilot: ProviderSettingsSnapshot.CopilotProviderSettings?\n    public var kilo: ProviderSettingsSnapshot.KiloProviderSettings?\n    public var kimi: ProviderSettingsSnapshot.KimiProviderSettings?\n    public var augment: ProviderSettingsSnapshot.AugmentProviderSettings?\n    public var amp: ProviderSettingsSnapshot.AmpProviderSettings?\n    public var ollama: ProviderSettingsSnapshot.OllamaProviderSettings?\n    public var jetbrains: ProviderSettingsSnapshot.JetBrainsProviderSettings?\n\n    public init(debugMenuEnabled: Bool = false, debugKeepCLISessionsAlive: Bool = false) {\n        self.debugMenuEnabled = debugMenuEnabled\n        self.debugKeepCLISessionsAlive = debugKeepCLISessionsAlive\n    }\n\n    public mutating func apply(_ contribution: ProviderSettingsSnapshotContribution) {\n        switch contribution {\n        case let .codex(value): self.codex = value\n        case let .claude(value): self.claude = value\n        case let .cursor(value): self.cursor = value\n        case let .opencode(value): self.opencode = value\n        case let .alibaba(value): self.alibaba = value\n        case let .factory(value): self.factory = value\n        case let .minimax(value): self.minimax = value\n        case let .zai(value): self.zai = value\n        case let .copilot(value): self.copilot = value\n        case let .kilo(value): self.kilo = value\n        case let .kimi(value): self.kimi = value\n        case let .augment(value): self.augment = value\n        case let .amp(value): self.amp = value\n        case let .ollama(value): self.ollama = value\n        case let .jetbrains(value): self.jetbrains = value\n        }\n    }\n\n    public func build() -> ProviderSettingsSnapshot {\n        ProviderSettingsSnapshot(\n            debugMenuEnabled: self.debugMenuEnabled,\n            debugKeepCLISessionsAlive: self.debugKeepCLISessionsAlive,\n            codex: self.codex,\n            claude: self.claude,\n            cursor: self.cursor,\n            opencode: self.opencode,\n            alibaba: self.alibaba,\n            factory: self.factory,\n            minimax: self.minimax,\n            zai: self.zai,\n            copilot: self.copilot,\n            kilo: self.kilo,\n            kimi: self.kimi,\n            augment: self.augment,\n            amp: self.amp,\n            ollama: self.ollama,\n            jetbrains: self.jetbrains)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/ProviderTokenResolver.swift",
    "content": "import Foundation\n\npublic enum ProviderTokenSource: String, Sendable {\n    case environment\n    case authFile\n}\n\npublic struct ProviderTokenResolution: Sendable {\n    public let token: String\n    public let source: ProviderTokenSource\n\n    public init(token: String, source: ProviderTokenSource) {\n        self.token = token\n        self.source = source\n    }\n}\n\npublic enum ProviderTokenResolver {\n    public static func zaiToken(environment: [String: String] = ProcessInfo.processInfo.environment) -> String? {\n        self.zaiResolution(environment: environment)?.token\n    }\n\n    public static func syntheticToken(\n        environment: [String: String] = ProcessInfo.processInfo.environment) -> String?\n    {\n        self.syntheticResolution(environment: environment)?.token\n    }\n\n    public static func copilotToken(environment: [String: String] = ProcessInfo.processInfo.environment) -> String? {\n        self.copilotResolution(environment: environment)?.token\n    }\n\n    public static func minimaxToken(environment: [String: String] = ProcessInfo.processInfo.environment) -> String? {\n        self.minimaxTokenResolution(environment: environment)?.token\n    }\n\n    public static func alibabaToken(environment: [String: String] = ProcessInfo.processInfo.environment) -> String? {\n        self.alibabaTokenResolution(environment: environment)?.token\n    }\n\n    public static func minimaxCookie(environment: [String: String] = ProcessInfo.processInfo.environment) -> String? {\n        self.minimaxCookieResolution(environment: environment)?.token\n    }\n\n    public static func kimiAuthToken(environment: [String: String] = ProcessInfo.processInfo.environment) -> String? {\n        self.kimiAuthResolution(environment: environment)?.token\n    }\n\n    public static func kimiK2Token(environment: [String: String] = ProcessInfo.processInfo.environment) -> String? {\n        self.kimiK2Resolution(environment: environment)?.token\n    }\n\n    public static func kiloToken(\n        environment: [String: String] = ProcessInfo.processInfo.environment,\n        authFileURL: URL? = nil) -> String?\n    {\n        self.kiloResolution(environment: environment, authFileURL: authFileURL)?.token\n    }\n\n    public static func warpToken(environment: [String: String] = ProcessInfo.processInfo.environment) -> String? {\n        self.warpResolution(environment: environment)?.token\n    }\n\n    public static func openRouterToken(environment: [String: String] = ProcessInfo.processInfo.environment) -> String? {\n        self.openRouterResolution(environment: environment)?.token\n    }\n\n    public static func zaiResolution(\n        environment: [String: String] = ProcessInfo.processInfo.environment) -> ProviderTokenResolution?\n    {\n        self.resolveEnv(ZaiSettingsReader.apiToken(environment: environment))\n    }\n\n    public static func syntheticResolution(\n        environment: [String: String] = ProcessInfo.processInfo.environment) -> ProviderTokenResolution?\n    {\n        self.resolveEnv(SyntheticSettingsReader.apiKey(environment: environment))\n    }\n\n    public static func copilotResolution(\n        environment: [String: String] = ProcessInfo.processInfo.environment) -> ProviderTokenResolution?\n    {\n        self.resolveEnv(self.cleaned(environment[\"COPILOT_API_TOKEN\"]))\n    }\n\n    public static func minimaxTokenResolution(\n        environment: [String: String] = ProcessInfo.processInfo.environment) -> ProviderTokenResolution?\n    {\n        self.resolveEnv(MiniMaxAPISettingsReader.apiToken(environment: environment))\n    }\n\n    public static func alibabaTokenResolution(\n        environment: [String: String] = ProcessInfo.processInfo.environment) -> ProviderTokenResolution?\n    {\n        self.resolveEnv(AlibabaCodingPlanSettingsReader.apiToken(environment: environment))\n    }\n\n    public static func minimaxCookieResolution(\n        environment: [String: String] = ProcessInfo.processInfo.environment) -> ProviderTokenResolution?\n    {\n        self.resolveEnv(MiniMaxSettingsReader.cookieHeader(environment: environment))\n    }\n\n    public static func kimiAuthResolution(\n        environment: [String: String] = ProcessInfo.processInfo.environment) -> ProviderTokenResolution?\n    {\n        if let resolution = self.resolveEnv(KimiSettingsReader.authToken(environment: environment)) {\n            return resolution\n        }\n        #if os(macOS)\n        do {\n            let session = try KimiCookieImporter.importSession()\n            if let token = session.authToken {\n                return ProviderTokenResolution(token: token, source: .environment)\n            }\n        } catch {\n            // No browser cookies found, continue to fallback\n        }\n        #endif\n        return nil\n    }\n\n    public static func kimiK2Resolution(\n        environment: [String: String] = ProcessInfo.processInfo.environment) -> ProviderTokenResolution?\n    {\n        self.resolveEnv(KimiK2SettingsReader.apiKey(environment: environment))\n    }\n\n    public static func kiloResolution(\n        environment: [String: String] = ProcessInfo.processInfo.environment,\n        authFileURL: URL? = nil) -> ProviderTokenResolution?\n    {\n        if let resolution = self.resolveEnv(KiloSettingsReader.apiKey(environment: environment)) {\n            return resolution\n        }\n        if let token = KiloSettingsReader.authToken(authFileURL: authFileURL) {\n            return ProviderTokenResolution(token: token, source: .authFile)\n        }\n        return nil\n    }\n\n    public static func warpResolution(\n        environment: [String: String] = ProcessInfo.processInfo.environment) -> ProviderTokenResolution?\n    {\n        self.resolveEnv(WarpSettingsReader.apiKey(environment: environment))\n    }\n\n    public static func openRouterResolution(\n        environment: [String: String] = ProcessInfo.processInfo.environment) -> ProviderTokenResolution?\n    {\n        self.resolveEnv(OpenRouterSettingsReader.apiToken(environment: environment))\n    }\n\n    private static func cleaned(_ raw: String?) -> String? {\n        guard var value = raw?.trimmingCharacters(in: .whitespacesAndNewlines), !value.isEmpty else {\n            return nil\n        }\n\n        if (value.hasPrefix(\"\\\"\") && value.hasSuffix(\"\\\"\")) ||\n            (value.hasPrefix(\"'\") && value.hasSuffix(\"'\"))\n        {\n            value.removeFirst()\n            value.removeLast()\n        }\n\n        value = value.trimmingCharacters(in: .whitespacesAndNewlines)\n        return value.isEmpty ? nil : value\n    }\n\n    private static func resolveEnv(_ token: String?) -> ProviderTokenResolution? {\n        guard let token else { return nil }\n        return ProviderTokenResolution(token: token, source: .environment)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/ProviderVersionDetector.swift",
    "content": "#if canImport(Darwin)\nimport Darwin\n#else\nimport Glibc\n#endif\nimport Foundation\n\npublic enum ProviderVersionDetector {\n    public static func claudeVersion() -> String? {\n        guard let path = TTYCommandRunner.which(\"claude\") else { return nil }\n        do {\n            let out = try TTYCommandRunner().run(\n                binary: path,\n                send: \"\",\n                options: TTYCommandRunner.Options(\n                    timeout: 5.0,\n                    extraArgs: [\"--allowed-tools\", \"\", \"--version\"],\n                    initialDelay: 0.0)).text\n            let trimmed = TextParsing.stripANSICodes(out).trimmingCharacters(in: .whitespacesAndNewlines)\n            return trimmed.isEmpty ? nil : trimmed\n        } catch {\n            return nil\n        }\n    }\n\n    public static func codexVersion() -> String? {\n        guard let path = TTYCommandRunner.which(\"codex\") else { return nil }\n        let candidates = [\n            [\"--version\"],\n            [\"version\"],\n            [\"-v\"],\n        ]\n        for args in candidates {\n            if let version = Self.run(path: path, args: args) { return version }\n        }\n        return nil\n    }\n\n    public static func geminiVersion() -> String? {\n        let env = ProcessInfo.processInfo.environment\n        guard let path = BinaryLocator.resolveGeminiBinary(env: env, loginPATH: nil)\n            ?? TTYCommandRunner.which(\"gemini\") else { return nil }\n        let candidates = [\n            [\"--version\"],\n            [\"-v\"],\n        ]\n        for args in candidates {\n            if let version = Self.run(path: path, args: args) { return version }\n        }\n        return nil\n    }\n\n    static func run(path: String, args: [String], timeout: TimeInterval = 2.0) -> String? {\n        let proc = Process()\n        proc.executableURL = URL(fileURLWithPath: path)\n        proc.arguments = args\n        let out = Pipe()\n        proc.standardOutput = out\n        proc.standardError = Pipe()\n        proc.standardInput = nil\n\n        let exitSemaphore = DispatchSemaphore(value: 0)\n        proc.terminationHandler = { _ in\n            exitSemaphore.signal()\n        }\n\n        do {\n            try proc.run()\n        } catch {\n            return nil\n        }\n\n        let didExit = exitSemaphore.wait(timeout: .now() + timeout) == .success\n        if !didExit, !Self.forceExit(proc, exitSemaphore: exitSemaphore) {\n            return nil\n        }\n\n        let data = out.fileHandleForReading.readDataToEndOfFile()\n        guard proc.terminationStatus == 0,\n              let text = String(data: data, encoding: .utf8)?\n                  .split(whereSeparator: \\.isNewline).first\n        else { return nil }\n        let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)\n        return trimmed.isEmpty ? nil : trimmed\n    }\n\n    private static func forceExit(_ proc: Process, exitSemaphore: DispatchSemaphore) -> Bool {\n        guard proc.isRunning else { return true }\n\n        proc.terminate()\n        if exitSemaphore.wait(timeout: .now() + 0.5) == .success {\n            return true\n        }\n\n        guard proc.isRunning else { return true }\n        kill(proc.processIdentifier, SIGKILL)\n        return exitSemaphore.wait(timeout: .now() + 1.0) == .success\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Providers.swift",
    "content": "import Foundation\nimport SweetCookieKit\n\n// swiftformat:disable sortDeclarations\npublic enum UsageProvider: String, CaseIterable, Sendable, Codable {\n    case codex\n    case claude\n    case cursor\n    case opencode\n    case alibaba\n    case factory\n    case gemini\n    case antigravity\n    case copilot\n    case zai\n    case minimax\n    case kimi\n    case kilo\n    case kiro\n    case vertexai\n    case augment\n    case jetbrains\n    case kimik2\n    case amp\n    case ollama\n    case synthetic\n    case warp\n    case openrouter\n}\n\n// swiftformat:enable sortDeclarations\n\npublic enum IconStyle: Sendable, CaseIterable {\n    case codex\n    case claude\n    case zai\n    case minimax\n    case gemini\n    case antigravity\n    case cursor\n    case opencode\n    case alibaba\n    case factory\n    case copilot\n    case kimi\n    case kimik2\n    case kilo\n    case kiro\n    case vertexai\n    case augment\n    case jetbrains\n    case amp\n    case ollama\n    case synthetic\n    case warp\n    case openrouter\n    case combined\n}\n\npublic struct ProviderMetadata: Sendable {\n    public let id: UsageProvider\n    public let displayName: String\n    public let sessionLabel: String\n    public let weeklyLabel: String\n    public let opusLabel: String?\n    public let supportsOpus: Bool\n    public let supportsCredits: Bool\n    public let creditsHint: String\n    public let toggleTitle: String\n    public let cliName: String\n    public let defaultEnabled: Bool\n    public let isPrimaryProvider: Bool\n    public let usesAccountFallback: Bool\n    public let browserCookieOrder: BrowserCookieImportOrder?\n    public let dashboardURL: String?\n    public let subscriptionDashboardURL: String?\n    /// Statuspage.io base URL for incident polling (append /api/v2/status.json).\n    public let statusPageURL: String?\n    /// Browser-only status link (no API polling); used when statusPageURL is nil.\n    public let statusLinkURL: String?\n    /// Google Workspace product ID for status polling (appsstatus dashboard).\n    public let statusWorkspaceProductID: String?\n\n    public init(\n        id: UsageProvider,\n        displayName: String,\n        sessionLabel: String,\n        weeklyLabel: String,\n        opusLabel: String?,\n        supportsOpus: Bool,\n        supportsCredits: Bool,\n        creditsHint: String,\n        toggleTitle: String,\n        cliName: String,\n        defaultEnabled: Bool,\n        isPrimaryProvider: Bool = false,\n        usesAccountFallback: Bool = false,\n        browserCookieOrder: BrowserCookieImportOrder? = nil,\n        dashboardURL: String?,\n        subscriptionDashboardURL: String? = nil,\n        statusPageURL: String?,\n        statusLinkURL: String? = nil,\n        statusWorkspaceProductID: String? = nil)\n    {\n        self.id = id\n        self.displayName = displayName\n        self.sessionLabel = sessionLabel\n        self.weeklyLabel = weeklyLabel\n        self.opusLabel = opusLabel\n        self.supportsOpus = supportsOpus\n        self.supportsCredits = supportsCredits\n        self.creditsHint = creditsHint\n        self.toggleTitle = toggleTitle\n        self.cliName = cliName\n        self.defaultEnabled = defaultEnabled\n        self.isPrimaryProvider = isPrimaryProvider\n        self.usesAccountFallback = usesAccountFallback\n        self.browserCookieOrder = browserCookieOrder\n        self.dashboardURL = dashboardURL\n        self.subscriptionDashboardURL = subscriptionDashboardURL\n        self.statusPageURL = statusPageURL\n        self.statusLinkURL = statusLinkURL\n        self.statusWorkspaceProductID = statusWorkspaceProductID\n    }\n}\n\npublic enum ProviderDefaults {\n    public static var metadata: [UsageProvider: ProviderMetadata] {\n        ProviderDescriptorRegistry.metadata\n    }\n}\n\npublic enum ProviderBrowserCookieDefaults {\n    public static var defaultImportOrder: BrowserCookieImportOrder? {\n        #if os(macOS)\n        Browser.defaultImportOrder\n        #else\n        nil\n        #endif\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Synthetic/SyntheticProviderDescriptor.swift",
    "content": "import CodexBarMacroSupport\nimport Foundation\n\n@ProviderDescriptorRegistration\n@ProviderDescriptorDefinition\npublic enum SyntheticProviderDescriptor {\n    static func makeDescriptor() -> ProviderDescriptor {\n        ProviderDescriptor(\n            id: .synthetic,\n            metadata: ProviderMetadata(\n                id: .synthetic,\n                displayName: \"Synthetic\",\n                sessionLabel: \"Quota\",\n                weeklyLabel: \"Usage\",\n                opusLabel: nil,\n                supportsOpus: false,\n                supportsCredits: false,\n                creditsHint: \"\",\n                toggleTitle: \"Show Synthetic usage\",\n                cliName: \"synthetic\",\n                defaultEnabled: false,\n                isPrimaryProvider: false,\n                usesAccountFallback: false,\n                dashboardURL: nil,\n                statusPageURL: nil),\n            branding: ProviderBranding(\n                iconStyle: .synthetic,\n                iconResourceName: \"ProviderIcon-synthetic\",\n                color: ProviderColor(red: 20 / 255, green: 20 / 255, blue: 20 / 255)),\n            tokenCost: ProviderTokenCostConfig(\n                supportsTokenCost: false,\n                noDataMessage: { \"Synthetic cost summary is not supported.\" }),\n            fetchPlan: ProviderFetchPlan(\n                sourceModes: [.auto, .api],\n                pipeline: ProviderFetchPipeline(resolveStrategies: { _ in [SyntheticAPIFetchStrategy()] })),\n            cli: ProviderCLIConfig(\n                name: \"synthetic\",\n                aliases: [\"synthetic.new\"],\n                versionDetector: nil))\n    }\n}\n\nstruct SyntheticAPIFetchStrategy: ProviderFetchStrategy {\n    let id: String = \"synthetic.api\"\n    let kind: ProviderFetchKind = .apiToken\n\n    func isAvailable(_ context: ProviderFetchContext) async -> Bool {\n        Self.resolveToken(environment: context.env) != nil\n    }\n\n    func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {\n        guard let apiKey = Self.resolveToken(environment: context.env) else {\n            throw SyntheticSettingsError.missingToken\n        }\n        let usage = try await SyntheticUsageFetcher.fetchUsage(apiKey: apiKey)\n        return self.makeResult(\n            usage: usage.toUsageSnapshot(),\n            sourceLabel: \"api\")\n    }\n\n    func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool {\n        false\n    }\n\n    private static func resolveToken(environment: [String: String]) -> String? {\n        ProviderTokenResolver.syntheticToken(environment: environment)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Synthetic/SyntheticSettingsReader.swift",
    "content": "import Foundation\n\npublic struct SyntheticSettingsReader: Sendable {\n    public static let apiKeyKey = \"SYNTHETIC_API_KEY\"\n\n    public static func apiKey(\n        environment: [String: String] = ProcessInfo.processInfo.environment) -> String?\n    {\n        if let token = self.cleaned(environment[apiKeyKey]) { return token }\n        return nil\n    }\n\n    static func cleaned(_ raw: String?) -> String? {\n        guard var value = raw?.trimmingCharacters(in: .whitespacesAndNewlines), !value.isEmpty else {\n            return nil\n        }\n\n        if (value.hasPrefix(\"\\\"\") && value.hasSuffix(\"\\\"\")) ||\n            (value.hasPrefix(\"'\") && value.hasSuffix(\"'\"))\n        {\n            value.removeFirst()\n            value.removeLast()\n        }\n\n        value = value.trimmingCharacters(in: .whitespacesAndNewlines)\n        return value.isEmpty ? nil : value\n    }\n}\n\npublic enum SyntheticSettingsError: LocalizedError, Sendable {\n    case missingToken\n\n    public var errorDescription: String? {\n        switch self {\n        case .missingToken:\n            \"Synthetic API key not found. Set apiKey in ~/.codexbar/config.json or SYNTHETIC_API_KEY.\"\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Synthetic/SyntheticUsageStats.swift",
    "content": "import Foundation\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n\npublic struct SyntheticQuotaEntry: Sendable {\n    public let label: String?\n    public let usedPercent: Double\n    public let windowMinutes: Int?\n    public let resetsAt: Date?\n    public let resetDescription: String?\n\n    public init(\n        label: String?,\n        usedPercent: Double,\n        windowMinutes: Int?,\n        resetsAt: Date?,\n        resetDescription: String?)\n    {\n        self.label = label\n        self.usedPercent = usedPercent\n        self.windowMinutes = windowMinutes\n        self.resetsAt = resetsAt\n        self.resetDescription = resetDescription\n    }\n}\n\npublic struct SyntheticUsageSnapshot: Sendable {\n    public let quotas: [SyntheticQuotaEntry]\n    public let planName: String?\n    public let updatedAt: Date\n\n    public init(quotas: [SyntheticQuotaEntry], planName: String?, updatedAt: Date) {\n        self.quotas = quotas\n        self.planName = planName\n        self.updatedAt = updatedAt\n    }\n}\n\nextension SyntheticUsageSnapshot {\n    public func toUsageSnapshot() -> UsageSnapshot {\n        let primaryEntry = self.quotas.first\n        let secondaryEntry = self.quotas.dropFirst().first\n\n        let primary = primaryEntry.map(Self.rateWindow(for:))\n        let secondary = secondaryEntry.map(Self.rateWindow(for:))\n\n        let planName = self.planName?.trimmingCharacters(in: .whitespacesAndNewlines)\n        let loginMethod = (planName?.isEmpty ?? true) ? nil : planName\n        let identity = ProviderIdentitySnapshot(\n            providerID: .synthetic,\n            accountEmail: nil,\n            accountOrganization: nil,\n            loginMethod: loginMethod)\n\n        return UsageSnapshot(\n            primary: primary,\n            secondary: secondary,\n            tertiary: nil,\n            providerCost: nil,\n            updatedAt: self.updatedAt,\n            identity: identity)\n    }\n\n    private static func rateWindow(for quota: SyntheticQuotaEntry) -> RateWindow {\n        RateWindow(\n            usedPercent: quota.usedPercent,\n            windowMinutes: quota.windowMinutes,\n            resetsAt: quota.resetsAt,\n            resetDescription: quota.resetDescription)\n    }\n}\n\npublic struct SyntheticUsageFetcher: Sendable {\n    private static let log = CodexBarLog.logger(LogCategories.syntheticUsage)\n    private static let quotaAPIURL = \"https://api.synthetic.new/v2/quotas\"\n\n    public static func fetchUsage(apiKey: String, now: Date = Date()) async throws -> SyntheticUsageSnapshot {\n        guard !apiKey.isEmpty else {\n            throw SyntheticUsageError.invalidCredentials\n        }\n\n        var request = URLRequest(url: URL(string: Self.quotaAPIURL)!)\n        request.httpMethod = \"GET\"\n        request.setValue(\"Bearer \\(apiKey)\", forHTTPHeaderField: \"Authorization\")\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Accept\")\n\n        let (data, response) = try await URLSession.shared.data(for: request)\n\n        guard let httpResponse = response as? HTTPURLResponse else {\n            throw SyntheticUsageError.networkError(\"Invalid response\")\n        }\n\n        guard httpResponse.statusCode == 200 else {\n            let errorMessage = String(data: data, encoding: .utf8) ?? \"Unknown error\"\n            Self.log.error(\"Synthetic API returned \\(httpResponse.statusCode): \\(errorMessage)\")\n            if httpResponse.statusCode == 401 || httpResponse.statusCode == 403 {\n                throw SyntheticUsageError.invalidCredentials\n            }\n            throw SyntheticUsageError.apiError(\"HTTP \\(httpResponse.statusCode): \\(errorMessage)\")\n        }\n\n        do {\n            return try SyntheticUsageParser.parse(data: data, now: now)\n        } catch let error as SyntheticUsageError {\n            throw error\n        } catch {\n            Self.log.error(\"Synthetic parsing error: \\(error.localizedDescription)\")\n            throw SyntheticUsageError.parseFailed(error.localizedDescription)\n        }\n    }\n}\n\nprivate final class SyntheticISO8601FormatterBox: @unchecked Sendable {\n    let lock = NSLock()\n    let withFractional: ISO8601DateFormatter = {\n        let formatter = ISO8601DateFormatter()\n        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]\n        return formatter\n    }()\n\n    let plain: ISO8601DateFormatter = {\n        let formatter = ISO8601DateFormatter()\n        formatter.formatOptions = [.withInternetDateTime]\n        return formatter\n    }()\n}\n\nprivate enum SyntheticTimestampParser {\n    static let box = SyntheticISO8601FormatterBox()\n\n    static func parse(_ text: String) -> Date? {\n        self.box.lock.lock()\n        defer { self.box.lock.unlock() }\n        return self.box.withFractional.date(from: text) ?? self.box.plain.date(from: text)\n    }\n}\n\nenum SyntheticUsageParser {\n    static func parse(data: Data, now: Date = Date()) throws -> SyntheticUsageSnapshot {\n        let object = try JSONSerialization.jsonObject(with: data, options: [])\n\n        let root: [String: Any] = {\n            if let dict = object as? [String: Any] { return dict }\n            if let array = object as? [Any] { return [\"quotas\": array] }\n            return [:]\n        }()\n\n        let planName = self.planName(from: root)\n        let quotaObjects = self.quotaObjects(from: root)\n        let quotas = quotaObjects.compactMap { self.parseQuota($0) }\n\n        guard !quotas.isEmpty else {\n            throw SyntheticUsageError.parseFailed(\"Missing quota data.\")\n        }\n\n        return SyntheticUsageSnapshot(\n            quotas: quotas,\n            planName: planName,\n            updatedAt: now)\n    }\n\n    private static func quotaObjects(from root: [String: Any]) -> [[String: Any]] {\n        let dataDict = root[\"data\"] as? [String: Any]\n        let candidates: [Any?] = [\n            root[\"quotas\"],\n            root[\"quota\"],\n            root[\"limits\"],\n            root[\"usage\"],\n            root[\"entries\"],\n            root[\"subscription\"],\n            root[\"data\"],\n            dataDict?[\"quotas\"],\n            dataDict?[\"quota\"],\n            dataDict?[\"limits\"],\n            dataDict?[\"usage\"],\n            dataDict?[\"entries\"],\n            dataDict?[\"subscription\"],\n        ]\n\n        for candidate in candidates {\n            if let array = candidate as? [[String: Any]] { return array }\n            if let array = candidate as? [Any] {\n                let dicts = array.compactMap { $0 as? [String: Any] }\n                if !dicts.isEmpty { return dicts }\n            }\n            if let dict = candidate as? [String: Any], self.isQuotaPayload(dict) {\n                return [dict]\n            }\n        }\n        return []\n    }\n\n    private static func planName(from root: [String: Any]) -> String? {\n        if let direct = self.firstString(in: root, keys: planKeys) { return direct }\n        if let dataDict = root[\"data\"] as? [String: Any],\n           let plan = self.firstString(in: dataDict, keys: planKeys)\n        {\n            return plan\n        }\n        return nil\n    }\n\n    private static func parseQuota(_ payload: [String: Any]) -> SyntheticQuotaEntry? {\n        let label = self.firstString(in: payload, keys: Self.labelKeys)\n\n        let percentUsed = self.normalizedPercent(\n            self.firstDouble(in: payload, keys: Self.percentUsedKeys))\n        let percentRemaining = self.normalizedPercent(\n            self.firstDouble(in: payload, keys: Self.percentRemainingKeys))\n\n        var usedPercent = percentUsed\n        if usedPercent == nil, let remaining = percentRemaining {\n            usedPercent = 100 - remaining\n        }\n\n        if usedPercent == nil {\n            var limit = self.firstDouble(in: payload, keys: Self.limitKeys)\n            var used = self.firstDouble(in: payload, keys: Self.usedKeys)\n            var remaining = self.firstDouble(in: payload, keys: Self.remainingKeys)\n\n            if limit == nil, let used, let remaining {\n                limit = used + remaining\n            }\n            if used == nil, let limit, let remaining {\n                used = limit - remaining\n            }\n            if remaining == nil, let limit, let used {\n                remaining = max(0, limit - used)\n            }\n\n            if let limit, let used, limit > 0 {\n                usedPercent = (used / limit) * 100\n            }\n        }\n\n        guard let usedPercent else { return nil }\n        let clamped = max(0, min(usedPercent, 100))\n\n        let windowMinutes = windowMinutes(from: payload)\n        let resetsAt = self.firstDate(in: payload, keys: self.resetKeys)\n        let resetDescription = resetsAt == nil ? self.windowDescription(minutes: windowMinutes) : nil\n\n        return SyntheticQuotaEntry(\n            label: label,\n            usedPercent: clamped,\n            windowMinutes: windowMinutes,\n            resetsAt: resetsAt,\n            resetDescription: resetDescription)\n    }\n\n    private static func isQuotaPayload(_ payload: [String: Any]) -> Bool {\n        let checks = [\n            Self.limitKeys,\n            Self.usedKeys,\n            Self.remainingKeys,\n            Self.percentUsedKeys,\n            Self.percentRemainingKeys,\n        ]\n        return checks.contains { self.firstDouble(in: payload, keys: $0) != nil }\n    }\n\n    private static func windowMinutes(from payload: [String: Any]) -> Int? {\n        if let minutes = self.firstInt(in: payload, keys: windowMinutesKeys) { return minutes }\n        if let hours = self.firstDouble(in: payload, keys: windowHoursKeys) {\n            return Int((hours * 60).rounded())\n        }\n        if let days = self.firstDouble(in: payload, keys: windowDaysKeys) {\n            return Int((days * 24 * 60).rounded())\n        }\n        if let seconds = self.firstDouble(in: payload, keys: windowSecondsKeys) {\n            return Int((seconds / 60).rounded())\n        }\n        return nil\n    }\n\n    private static func windowDescription(minutes: Int?) -> String? {\n        guard let minutes, minutes > 0 else { return nil }\n        let dayMinutes = 24 * 60\n        if minutes % dayMinutes == 0 {\n            let days = minutes / dayMinutes\n            return \"\\(days) day\\(days == 1 ? \"\" : \"s\") window\"\n        }\n        if minutes % 60 == 0 {\n            let hours = minutes / 60\n            return \"\\(hours) hour\\(hours == 1 ? \"\" : \"s\") window\"\n        }\n        return \"\\(minutes) minute\\(minutes == 1 ? \"\" : \"s\") window\"\n    }\n\n    private static func normalizedPercent(_ value: Double?) -> Double? {\n        guard let value else { return nil }\n        if value <= 1 { return value * 100 }\n        return value\n    }\n\n    private static func firstString(in payload: [String: Any], keys: [String]) -> String? {\n        for key in keys {\n            if let value = self.stringValue(payload[key]) { return value }\n        }\n        return nil\n    }\n\n    private static func firstDouble(in payload: [String: Any], keys: [String]) -> Double? {\n        for key in keys {\n            if let value = self.doubleValue(payload[key]) { return value }\n        }\n        return nil\n    }\n\n    private static func firstInt(in payload: [String: Any], keys: [String]) -> Int? {\n        for key in keys {\n            if let value = self.intValue(payload[key]) { return value }\n        }\n        return nil\n    }\n\n    private static func firstDate(in payload: [String: Any], keys: [String]) -> Date? {\n        for key in keys {\n            if let value = payload[key],\n               let date = self.dateValue(value)\n            {\n                return date\n            }\n        }\n        return nil\n    }\n\n    private static func stringValue(_ raw: Any?) -> String? {\n        guard let raw else { return nil }\n        if let string = raw as? String {\n            let trimmed = string.trimmingCharacters(in: .whitespacesAndNewlines)\n            return trimmed.isEmpty ? nil : trimmed\n        }\n        return nil\n    }\n\n    private static func doubleValue(_ raw: Any?) -> Double? {\n        switch raw {\n        case let number as Double:\n            return number\n        case let number as NSNumber:\n            return number.doubleValue\n        case let string as String:\n            let trimmed = string.trimmingCharacters(in: .whitespacesAndNewlines)\n            return Double(trimmed)\n        default:\n            return nil\n        }\n    }\n\n    private static func intValue(_ raw: Any?) -> Int? {\n        switch raw {\n        case let number as Int:\n            return number\n        case let number as NSNumber:\n            return number.intValue\n        case let string as String:\n            let trimmed = string.trimmingCharacters(in: .whitespacesAndNewlines)\n            return Int(trimmed)\n        default:\n            return nil\n        }\n    }\n\n    private static func dateValue(_ raw: Any) -> Date? {\n        if let number = self.doubleValue(raw) {\n            if number > 1_000_000_000_000 {\n                return Date(timeIntervalSince1970: number / 1000)\n            }\n            if number > 1_000_000_000 {\n                return Date(timeIntervalSince1970: number)\n            }\n        }\n        if let string = raw as? String {\n            if let number = Double(string.trimmingCharacters(in: .whitespacesAndNewlines)) {\n                return self.dateValue(number)\n            }\n            if let date = SyntheticTimestampParser.parse(string) {\n                return date\n            }\n        }\n        return nil\n    }\n\n    private static let planKeys = [\n        \"plan\",\n        \"planName\",\n        \"plan_name\",\n        \"subscription\",\n        \"subscriptionPlan\",\n        \"tier\",\n        \"package\",\n        \"packageName\",\n    ]\n\n    private static let labelKeys = [\n        \"name\",\n        \"label\",\n        \"type\",\n        \"period\",\n        \"scope\",\n        \"title\",\n        \"id\",\n    ]\n\n    private static let percentUsedKeys = [\n        \"percentUsed\",\n        \"usedPercent\",\n        \"usagePercent\",\n        \"usage_percent\",\n        \"used_percent\",\n        \"percent_used\",\n        \"percent\",\n    ]\n\n    private static let percentRemainingKeys = [\n        \"percentRemaining\",\n        \"remainingPercent\",\n        \"remaining_percent\",\n        \"percent_remaining\",\n    ]\n\n    private static let limitKeys = [\n        \"limit\",\n        \"quota\",\n        \"max\",\n        \"total\",\n        \"capacity\",\n        \"allowance\",\n    ]\n\n    private static let usedKeys = [\n        \"used\",\n        \"usage\",\n        \"requests\",\n        \"requestCount\",\n        \"request_count\",\n        \"consumed\",\n        \"spent\",\n    ]\n\n    private static let remainingKeys = [\n        \"remaining\",\n        \"left\",\n        \"available\",\n        \"balance\",\n    ]\n\n    private static let resetKeys = [\n        \"resetAt\",\n        \"reset_at\",\n        \"resetsAt\",\n        \"resets_at\",\n        \"renewAt\",\n        \"renew_at\",\n        \"renewsAt\",\n        \"renews_at\",\n        \"periodEnd\",\n        \"period_end\",\n        \"expiresAt\",\n        \"expires_at\",\n        \"endAt\",\n        \"end_at\",\n    ]\n\n    private static let windowMinutesKeys = [\n        \"windowMinutes\",\n        \"window_minutes\",\n        \"periodMinutes\",\n        \"period_minutes\",\n    ]\n\n    private static let windowHoursKeys = [\n        \"windowHours\",\n        \"window_hours\",\n        \"periodHours\",\n        \"period_hours\",\n    ]\n\n    private static let windowDaysKeys = [\n        \"windowDays\",\n        \"window_days\",\n        \"periodDays\",\n        \"period_days\",\n    ]\n\n    private static let windowSecondsKeys = [\n        \"windowSeconds\",\n        \"window_seconds\",\n        \"periodSeconds\",\n        \"period_seconds\",\n    ]\n}\n\npublic enum SyntheticUsageError: LocalizedError, Sendable {\n    case invalidCredentials\n    case networkError(String)\n    case apiError(String)\n    case parseFailed(String)\n\n    public var errorDescription: String? {\n        switch self {\n        case .invalidCredentials:\n            \"Invalid Synthetic API credentials\"\n        case let .networkError(message):\n            \"Synthetic network error: \\(message)\"\n        case let .apiError(message):\n            \"Synthetic API error: \\(message)\"\n        case let .parseFailed(message):\n            \"Failed to parse Synthetic response: \\(message)\"\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/VertexAI/VertexAIOAuth/VertexAIOAuthCredentials.swift",
    "content": "import Foundation\n\npublic struct VertexAIOAuthCredentials: Sendable {\n    public let accessToken: String\n    public let refreshToken: String\n    public let clientId: String\n    public let clientSecret: String\n    public let projectId: String?\n    public let email: String?\n    public let expiryDate: Date?\n\n    public init(\n        accessToken: String,\n        refreshToken: String,\n        clientId: String,\n        clientSecret: String,\n        projectId: String?,\n        email: String?,\n        expiryDate: Date?)\n    {\n        self.accessToken = accessToken\n        self.refreshToken = refreshToken\n        self.clientId = clientId\n        self.clientSecret = clientSecret\n        self.projectId = projectId\n        self.email = email\n        self.expiryDate = expiryDate\n    }\n\n    public var needsRefresh: Bool {\n        guard let expiryDate else { return true }\n        // Refresh 5 minutes before expiry\n        return Date().addingTimeInterval(300) > expiryDate\n    }\n}\n\npublic enum VertexAIOAuthCredentialsError: LocalizedError, Sendable {\n    case notFound\n    case decodeFailed(String)\n    case missingTokens\n    case missingClientCredentials\n\n    public var errorDescription: String? {\n        switch self {\n        case .notFound:\n            \"gcloud credentials not found. Run `gcloud auth application-default login` to authenticate.\"\n        case let .decodeFailed(message):\n            \"Failed to decode gcloud credentials: \\(message)\"\n        case .missingTokens:\n            \"gcloud credentials exist but contain no tokens.\"\n        case .missingClientCredentials:\n            \"gcloud credentials missing client ID or secret.\"\n        }\n    }\n}\n\npublic enum VertexAIOAuthCredentialsStore {\n    private static var credentialsFilePath: URL {\n        let home = FileManager.default.homeDirectoryForCurrentUser\n        // gcloud application default credentials location\n        if let configDir = ProcessInfo.processInfo.environment[\"CLOUDSDK_CONFIG\"]?.trimmingCharacters(\n            in: .whitespacesAndNewlines),\n            !configDir.isEmpty\n        {\n            return URL(fileURLWithPath: configDir)\n                .appendingPathComponent(\"application_default_credentials.json\")\n        }\n        return home\n            .appendingPathComponent(\".config\")\n            .appendingPathComponent(\"gcloud\")\n            .appendingPathComponent(\"application_default_credentials.json\")\n    }\n\n    private static var projectFilePath: URL {\n        let home = FileManager.default.homeDirectoryForCurrentUser\n        if let configDir = ProcessInfo.processInfo.environment[\"CLOUDSDK_CONFIG\"]?.trimmingCharacters(\n            in: .whitespacesAndNewlines),\n            !configDir.isEmpty\n        {\n            return URL(fileURLWithPath: configDir)\n                .appendingPathComponent(\"configurations\")\n                .appendingPathComponent(\"config_default\")\n        }\n        return home\n            .appendingPathComponent(\".config\")\n            .appendingPathComponent(\"gcloud\")\n            .appendingPathComponent(\"configurations\")\n            .appendingPathComponent(\"config_default\")\n    }\n\n    public static func load() throws -> VertexAIOAuthCredentials {\n        let url = self.credentialsFilePath\n        guard FileManager.default.fileExists(atPath: url.path) else {\n            throw VertexAIOAuthCredentialsError.notFound\n        }\n\n        let data = try Data(contentsOf: url)\n        return try self.parse(data: data)\n    }\n\n    public static func parse(data: Data) throws -> VertexAIOAuthCredentials {\n        guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {\n            throw VertexAIOAuthCredentialsError.decodeFailed(\"Invalid JSON\")\n        }\n\n        // Check for service account credentials\n        if json[\"client_email\"] is String,\n           json[\"private_key\"] is String\n        {\n            // Service account - use JWT for access token (simplified)\n            throw VertexAIOAuthCredentialsError.decodeFailed(\n                \"Service account credentials not yet supported. Use `gcloud auth application-default login`.\")\n        }\n\n        // User credentials from gcloud auth application-default login\n        guard let clientId = json[\"client_id\"] as? String,\n              let clientSecret = json[\"client_secret\"] as? String\n        else {\n            throw VertexAIOAuthCredentialsError.missingClientCredentials\n        }\n\n        guard let refreshToken = json[\"refresh_token\"] as? String, !refreshToken.isEmpty else {\n            throw VertexAIOAuthCredentialsError.missingTokens\n        }\n\n        // Access token may not be present in the file; we'll need to refresh\n        let accessToken = json[\"access_token\"] as? String ?? \"\"\n\n        // Try to get project ID from gcloud config\n        let projectId = Self.loadProjectId()\n\n        // Try to extract email from ID token if present\n        let email = Self.extractEmailFromIdToken(json[\"id_token\"] as? String)\n\n        // Parse expiry if present\n        var expiryDate: Date?\n        if let expiryStr = json[\"token_expiry\"] as? String {\n            let formatter = ISO8601DateFormatter()\n            expiryDate = formatter.date(from: expiryStr)\n        }\n\n        return VertexAIOAuthCredentials(\n            accessToken: accessToken,\n            refreshToken: refreshToken,\n            clientId: clientId,\n            clientSecret: clientSecret,\n            projectId: projectId,\n            email: email,\n            expiryDate: expiryDate)\n    }\n\n    public static func save(_ credentials: VertexAIOAuthCredentials) throws {\n        // We don't modify gcloud's credentials file; just cache the access token in memory\n        // The refresh happens on each app launch if needed\n    }\n\n    private static func loadProjectId() -> String? {\n        let configPath = self.projectFilePath\n        guard let content = try? String(contentsOf: configPath, encoding: .utf8) else {\n            return nil\n        }\n\n        // Parse INI-style config for project\n        for line in content.components(separatedBy: .newlines) {\n            let trimmed = line.trimmingCharacters(in: .whitespaces)\n            if trimmed.hasPrefix(\"project\") {\n                let parts = trimmed.components(separatedBy: \"=\")\n                if parts.count >= 2 {\n                    return parts[1].trimmingCharacters(in: .whitespaces)\n                }\n            }\n        }\n\n        // Try environment variable\n        return ProcessInfo.processInfo.environment[\"GOOGLE_CLOUD_PROJECT\"]\n            ?? ProcessInfo.processInfo.environment[\"GCLOUD_PROJECT\"]\n            ?? ProcessInfo.processInfo.environment[\"CLOUDSDK_CORE_PROJECT\"]\n    }\n\n    private static func extractEmailFromIdToken(_ token: String?) -> String? {\n        guard let token, !token.isEmpty else { return nil }\n\n        let parts = token.components(separatedBy: \".\")\n        guard parts.count >= 2 else { return nil }\n\n        var payload = parts[1]\n            .replacingOccurrences(of: \"-\", with: \"+\")\n            .replacingOccurrences(of: \"_\", with: \"/\")\n\n        let remainder = payload.count % 4\n        if remainder > 0 {\n            payload += String(repeating: \"=\", count: 4 - remainder)\n        }\n\n        guard let data = Data(base64Encoded: payload, options: .ignoreUnknownCharacters),\n              let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any]\n        else {\n            return nil\n        }\n\n        return json[\"email\"] as? String\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/VertexAI/VertexAIOAuth/VertexAITokenRefresher.swift",
    "content": "import Foundation\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n\npublic enum VertexAITokenRefresher {\n    private static let tokenEndpoint = URL(string: \"https://oauth2.googleapis.com/token\")!\n\n    public enum RefreshError: LocalizedError, Sendable {\n        case expired\n        case revoked\n        case networkError(Error)\n        case invalidResponse(String)\n\n        public var errorDescription: String? {\n            switch self {\n            case .expired:\n                \"Refresh token expired. Run `gcloud auth application-default login` again.\"\n            case .revoked:\n                \"Refresh token was revoked. Run `gcloud auth application-default login` again.\"\n            case let .networkError(error):\n                \"Network error during token refresh: \\(error.localizedDescription)\"\n            case let .invalidResponse(message):\n                \"Invalid refresh response: \\(message)\"\n            }\n        }\n    }\n\n    public static func refresh(_ credentials: VertexAIOAuthCredentials) async throws -> VertexAIOAuthCredentials {\n        guard !credentials.refreshToken.isEmpty else {\n            throw RefreshError.invalidResponse(\"No refresh token available\")\n        }\n\n        var request = URLRequest(url: Self.tokenEndpoint)\n        request.httpMethod = \"POST\"\n        request.timeoutInterval = 30\n        request.setValue(\"application/x-www-form-urlencoded\", forHTTPHeaderField: \"Content-Type\")\n\n        let bodyParams = [\n            \"client_id\": credentials.clientId,\n            \"client_secret\": credentials.clientSecret,\n            \"refresh_token\": credentials.refreshToken,\n            \"grant_type\": \"refresh_token\",\n        ]\n\n        let bodyString = bodyParams\n            .map { \"\\($0.key)=\\($0.value.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? $0.value)\" }\n            .joined(separator: \"&\")\n        request.httpBody = bodyString.data(using: .utf8)\n\n        do {\n            let (data, response) = try await URLSession.shared.data(for: request)\n            guard let http = response as? HTTPURLResponse else {\n                throw RefreshError.invalidResponse(\"No HTTP response\")\n            }\n\n            if http.statusCode == 400 || http.statusCode == 401 {\n                if let errorCode = Self.extractErrorCode(from: data) {\n                    switch errorCode.lowercased() {\n                    case \"invalid_grant\":\n                        throw RefreshError.expired\n                    case \"unauthorized_client\":\n                        throw RefreshError.revoked\n                    default:\n                        throw RefreshError.invalidResponse(\"Error: \\(errorCode)\")\n                    }\n                }\n                throw RefreshError.expired\n            }\n\n            guard http.statusCode == 200 else {\n                throw RefreshError.invalidResponse(\"Status \\(http.statusCode)\")\n            }\n\n            guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {\n                throw RefreshError.invalidResponse(\"Invalid JSON\")\n            }\n\n            let newAccessToken = json[\"access_token\"] as? String ?? credentials.accessToken\n            let expiresIn = json[\"expires_in\"] as? Double ?? 3600\n            let newExpiryDate = Date().addingTimeInterval(expiresIn)\n\n            // Extract email from new ID token if present\n            let idToken = json[\"id_token\"] as? String\n            let email = Self.extractEmailFromIdToken(idToken) ?? credentials.email\n\n            return VertexAIOAuthCredentials(\n                accessToken: newAccessToken,\n                refreshToken: credentials.refreshToken,\n                clientId: credentials.clientId,\n                clientSecret: credentials.clientSecret,\n                projectId: credentials.projectId,\n                email: email,\n                expiryDate: newExpiryDate)\n        } catch let error as RefreshError {\n            throw error\n        } catch {\n            throw RefreshError.networkError(error)\n        }\n    }\n\n    private static func extractErrorCode(from data: Data) -> String? {\n        guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { return nil }\n        return json[\"error\"] as? String\n    }\n\n    private static func extractEmailFromIdToken(_ token: String?) -> String? {\n        guard let token, !token.isEmpty else { return nil }\n\n        let parts = token.components(separatedBy: \".\")\n        guard parts.count >= 2 else { return nil }\n\n        var payload = parts[1]\n            .replacingOccurrences(of: \"-\", with: \"+\")\n            .replacingOccurrences(of: \"_\", with: \"/\")\n\n        let remainder = payload.count % 4\n        if remainder > 0 {\n            payload += String(repeating: \"=\", count: 4 - remainder)\n        }\n\n        guard let data = Data(base64Encoded: payload, options: .ignoreUnknownCharacters),\n              let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any]\n        else {\n            return nil\n        }\n\n        return json[\"email\"] as? String\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/VertexAI/VertexAIOAuth/VertexAIUsageFetcher.swift",
    "content": "import Foundation\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n\npublic enum VertexAIFetchError: LocalizedError, Sendable {\n    case unauthorized\n    case forbidden\n    case noProject\n    case networkError(Error)\n    case invalidResponse(String)\n    case noData\n\n    public var errorDescription: String? {\n        switch self {\n        case .unauthorized:\n            \"Vertex AI request unauthorized. Run `gcloud auth application-default login`.\"\n        case .forbidden:\n            \"Access forbidden. Check your IAM permissions for Cloud Monitoring.\"\n        case .noProject:\n            \"No Google Cloud project configured. Run `gcloud config set project PROJECT_ID`.\"\n        case let .networkError(error):\n            \"Vertex AI network error: \\(error.localizedDescription)\"\n        case let .invalidResponse(message):\n            \"Vertex AI response was invalid: \\(message)\"\n        case .noData:\n            \"No Vertex AI usage data found for the current project.\"\n        }\n    }\n}\n\npublic struct VertexAIUsageResponse: Sendable {\n    public let requestsUsedPercent: Double\n    public let tokensUsedPercent: Double?\n    public let resetsAt: Date?\n    public let resetDescription: String?\n    public let rawData: String?\n\n    public init(\n        requestsUsedPercent: Double,\n        tokensUsedPercent: Double?,\n        resetsAt: Date?,\n        resetDescription: String?,\n        rawData: String?)\n    {\n        self.requestsUsedPercent = requestsUsedPercent\n        self.tokensUsedPercent = tokensUsedPercent\n        self.resetsAt = resetsAt\n        self.resetDescription = resetDescription\n        self.rawData = rawData\n    }\n}\n\npublic enum VertexAIUsageFetcher {\n    private static let log = CodexBarLog.logger(LogCategories.vertexAIFetcher)\n\n    // Cloud Monitoring API endpoint for time series\n    private static let monitoringEndpoint = \"https://monitoring.googleapis.com/v3/projects\"\n    private static let usageWindowSeconds: TimeInterval = 24 * 60 * 60\n\n    public static func fetchUsage(\n        accessToken: String,\n        projectId: String?) async throws -> VertexAIUsageResponse\n    {\n        guard let projectId, !projectId.isEmpty else {\n            throw VertexAIFetchError.noProject\n        }\n\n        return try await Self.fetchQuotaUsage(\n            accessToken: accessToken,\n            projectId: projectId)\n    }\n\n    private static func fetchQuotaUsage(\n        accessToken: String,\n        projectId: String) async throws -> VertexAIUsageResponse\n    {\n        let usageFilter = \"\"\"\n        metric.type=\"serviceruntime.googleapis.com/quota/allocation/usage\" \\\n        AND resource.type=\"consumer_quota\" \\\n        AND resource.label.service=\"aiplatform.googleapis.com\"\n        \"\"\"\n        let limitFilter = \"\"\"\n        metric.type=\"serviceruntime.googleapis.com/quota/limit\" \\\n        AND resource.type=\"consumer_quota\" \\\n        AND resource.label.service=\"aiplatform.googleapis.com\"\n        \"\"\"\n\n        let usageSeries = try await Self.fetchTimeSeries(\n            accessToken: accessToken,\n            projectId: projectId,\n            filter: usageFilter)\n        let limitSeries = try await Self.fetchTimeSeries(\n            accessToken: accessToken,\n            projectId: projectId,\n            filter: limitFilter)\n\n        let usageByKey = Self.aggregate(series: usageSeries)\n        let limitByKey = Self.aggregate(series: limitSeries)\n\n        guard !usageByKey.isEmpty, !limitByKey.isEmpty else {\n            throw VertexAIFetchError.noData\n        }\n\n        var maxPercent: Double?\n        var matchedCount = 0\n        var matchedKeys: Set<QuotaKey> = []\n        for (key, limit) in limitByKey {\n            guard limit > 0, let usage = usageByKey[key] else { continue }\n            matchedKeys.insert(key)\n            matchedCount += 1\n            let percent = (usage / limit) * 100.0\n            maxPercent = max(maxPercent ?? percent, percent)\n        }\n\n        guard let usedPercent = maxPercent, matchedCount > 0 else {\n            throw VertexAIFetchError.noData\n        }\n\n        let unmatchedUsage = Set(usageByKey.keys).subtracting(matchedKeys).count\n        let unmatchedLimit = Set(limitByKey.keys).subtracting(matchedKeys).count\n        Self.log.debug(\"Quota series preview\", metadata: [\n            \"usageKeys\": Self.previewKeys(usageByKey),\n            \"limitKeys\": Self.previewKeys(limitByKey),\n        ])\n        Self.log.info(\"Parsed quota\", metadata: [\n            \"usedPercent\": \"\\(usedPercent)\",\n            \"usageSeries\": \"\\(usageByKey.count)\",\n            \"limitSeries\": \"\\(limitByKey.count)\",\n            \"matchedSeries\": \"\\(matchedCount)\",\n            \"unmatchedUsage\": \"\\(unmatchedUsage)\",\n            \"unmatchedLimit\": \"\\(unmatchedLimit)\",\n        ])\n\n        return VertexAIUsageResponse(\n            requestsUsedPercent: usedPercent,\n            tokensUsedPercent: nil,\n            resetsAt: nil,\n            resetDescription: nil,\n            rawData: nil)\n    }\n\n    private struct MonitoringTimeSeriesResponse: Decodable {\n        let timeSeries: [MonitoringTimeSeries]?\n        let nextPageToken: String?\n    }\n\n    private struct MonitoringTimeSeries: Decodable {\n        let metric: MonitoringMetric\n        let resource: MonitoringResource\n        let points: [MonitoringPoint]\n    }\n\n    private struct MonitoringMetric: Decodable {\n        let type: String?\n        let labels: [String: String]?\n    }\n\n    private struct MonitoringResource: Decodable {\n        let type: String?\n        let labels: [String: String]?\n    }\n\n    private struct MonitoringPoint: Decodable {\n        let value: MonitoringValue\n    }\n\n    private struct MonitoringValue: Decodable {\n        let doubleValue: Double?\n        let int64Value: String?\n    }\n\n    private struct QuotaKey: Hashable {\n        let quotaMetric: String\n        let limitName: String\n        let location: String\n    }\n\n    private static func fetchTimeSeries(\n        accessToken: String,\n        projectId: String,\n        filter: String) async throws -> [MonitoringTimeSeries]\n    {\n        let now = Date()\n        let start = now.addingTimeInterval(-Self.usageWindowSeconds)\n        let formatter = ISO8601DateFormatter()\n        var pageToken: String?\n        var allSeries: [MonitoringTimeSeries] = []\n\n        repeat {\n            guard var components = URLComponents(\n                string: \"\\(Self.monitoringEndpoint)/\\(projectId)/timeSeries\")\n            else {\n                throw VertexAIFetchError.invalidResponse(\"Invalid Monitoring URL\")\n            }\n\n            var queryItems = [\n                URLQueryItem(name: \"filter\", value: filter),\n                URLQueryItem(name: \"interval.startTime\", value: formatter.string(from: start)),\n                URLQueryItem(name: \"interval.endTime\", value: formatter.string(from: now)),\n                URLQueryItem(name: \"aggregation.alignmentPeriod\", value: \"3600s\"),\n                URLQueryItem(name: \"aggregation.perSeriesAligner\", value: \"ALIGN_MAX\"),\n                URLQueryItem(name: \"view\", value: \"FULL\"),\n            ]\n            if let pageToken {\n                queryItems.append(URLQueryItem(name: \"pageToken\", value: pageToken))\n            }\n            components.queryItems = queryItems\n\n            guard let url = components.url else {\n                throw VertexAIFetchError.invalidResponse(\"Invalid Monitoring URL\")\n            }\n\n            var request = URLRequest(url: url)\n            request.setValue(\"Bearer \\(accessToken)\", forHTTPHeaderField: \"Authorization\")\n            request.timeoutInterval = 30\n\n            let data: Data\n            let response: URLResponse\n\n            do {\n                (data, response) = try await URLSession.shared.data(for: request)\n            } catch {\n                throw VertexAIFetchError.networkError(error)\n            }\n\n            guard let http = response as? HTTPURLResponse else {\n                throw VertexAIFetchError.invalidResponse(\"No HTTP response\")\n            }\n\n            switch http.statusCode {\n            case 401:\n                throw VertexAIFetchError.unauthorized\n            case 403:\n                throw VertexAIFetchError.forbidden\n            case 200:\n                break\n            default:\n                let body = String(data: data, encoding: .utf8) ?? \"\"\n                throw VertexAIFetchError.invalidResponse(\"HTTP \\(http.statusCode): \\(body)\")\n            }\n\n            let decoded = try JSONDecoder().decode(MonitoringTimeSeriesResponse.self, from: data)\n            if let series = decoded.timeSeries {\n                allSeries.append(contentsOf: series)\n            }\n            pageToken = decoded.nextPageToken?.isEmpty == false ? decoded.nextPageToken : nil\n        } while pageToken != nil\n\n        return allSeries\n    }\n\n    private static func aggregate(series: [MonitoringTimeSeries]) -> [QuotaKey: Double] {\n        var buckets: [QuotaKey: Double] = [:]\n\n        for entry in series {\n            guard let key = Self.quotaKey(from: entry),\n                  let value = Self.maxPointValue(from: entry.points)\n            else {\n                continue\n            }\n            buckets[key] = max(buckets[key] ?? 0, value)\n        }\n\n        return buckets\n    }\n\n    private static func quotaKey(from series: MonitoringTimeSeries) -> QuotaKey? {\n        let metricLabels = series.metric.labels ?? [:]\n        let resourceLabels = series.resource.labels ?? [:]\n        let quotaMetric = metricLabels[\"quota_metric\"]\n            ?? resourceLabels[\"quota_id\"]\n        guard let quotaMetric, !quotaMetric.isEmpty else { return nil }\n        let limitName = metricLabels[\"limit_name\"] ?? \"\"\n        let location = resourceLabels[\"location\"] ?? \"global\"\n        return QuotaKey(quotaMetric: quotaMetric, limitName: limitName, location: location)\n    }\n\n    private static func maxPointValue(from points: [MonitoringPoint]) -> Double? {\n        points.compactMap(self.pointValue).max()\n    }\n\n    private static func pointValue(from point: MonitoringPoint) -> Double? {\n        if let doubleValue = point.value.doubleValue { return doubleValue }\n        if let int64Value = point.value.int64Value { return Double(int64Value) }\n        return nil\n    }\n\n    private static func previewKeys(_ map: [QuotaKey: Double], maxCount: Int = 3) -> String {\n        guard !map.isEmpty else { return \"none\" }\n        let keys = map.keys.prefix(maxCount).map { key in\n            \"\\(key.quotaMetric)|\\(key.limitName)|\\(key.location)\"\n        }\n        let suffix = map.count > maxCount ? \" +\\(map.count - maxCount)\" : \"\"\n        return keys.joined(separator: \", \") + suffix\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/VertexAI/VertexAIProviderDescriptor.swift",
    "content": "import CodexBarMacroSupport\nimport Foundation\n\n@ProviderDescriptorRegistration\n@ProviderDescriptorDefinition\npublic enum VertexAIProviderDescriptor {\n    static func makeDescriptor() -> ProviderDescriptor {\n        ProviderDescriptor(\n            id: .vertexai,\n            metadata: ProviderMetadata(\n                id: .vertexai,\n                displayName: \"Vertex AI\",\n                sessionLabel: \"Requests\",\n                weeklyLabel: \"Tokens\",\n                opusLabel: nil,\n                supportsOpus: false,\n                supportsCredits: false,\n                creditsHint: \"\",\n                toggleTitle: \"Show Vertex AI usage\",\n                cliName: \"vertexai\",\n                defaultEnabled: false,\n                isPrimaryProvider: false,\n                usesAccountFallback: false,\n                dashboardURL: \"https://console.cloud.google.com/vertex-ai\",\n                statusPageURL: nil,\n                statusLinkURL: \"https://status.cloud.google.com\"),\n            branding: ProviderBranding(\n                iconStyle: .vertexai,\n                iconResourceName: \"ProviderIcon-vertexai\",\n                color: ProviderColor(red: 66 / 255, green: 133 / 255, blue: 244 / 255)),\n            tokenCost: ProviderTokenCostConfig(\n                supportsTokenCost: true,\n                noDataMessage: { \"No Vertex AI cost data found in Claude logs. Ensure entries include Vertex metadata.\"\n                }),\n            fetchPlan: ProviderFetchPlan(\n                sourceModes: [.auto, .oauth],\n                pipeline: ProviderFetchPipeline(resolveStrategies: { _ in [VertexAIOAuthFetchStrategy()] })),\n            cli: ProviderCLIConfig(\n                name: \"vertexai\",\n                versionDetector: nil))\n    }\n}\n\nstruct VertexAIOAuthFetchStrategy: ProviderFetchStrategy {\n    let id: String = \"vertexai.oauth\"\n    let kind: ProviderFetchKind = .oauth\n\n    func isAvailable(_: ProviderFetchContext) async -> Bool {\n        (try? VertexAIOAuthCredentialsStore.load()) != nil\n    }\n\n    func fetch(_: ProviderFetchContext) async throws -> ProviderFetchResult {\n        var credentials = try VertexAIOAuthCredentialsStore.load()\n\n        // Refresh token if expired\n        if credentials.needsRefresh {\n            credentials = try await VertexAITokenRefresher.refresh(credentials)\n            try VertexAIOAuthCredentialsStore.save(credentials)\n        }\n\n        // Fetch quota usage from Cloud Monitoring. If no data is found (e.g., no recent\n        // Vertex AI requests), return an empty snapshot so token costs can still display.\n        let usage: VertexAIUsageResponse?\n        do {\n            usage = try await VertexAIUsageFetcher.fetchUsage(\n                accessToken: credentials.accessToken,\n                projectId: credentials.projectId)\n        } catch VertexAIFetchError.noData {\n            // No quota data is fine - token costs from local logs can still be shown.\n            usage = nil\n        }\n\n        return self.makeResult(\n            usage: Self.mapUsage(usage, credentials: credentials),\n            sourceLabel: \"oauth\")\n    }\n\n    func shouldFallback(on error: Error, context _: ProviderFetchContext) -> Bool {\n        if error is VertexAIOAuthCredentialsError { return true }\n        if let fetchError = error as? VertexAIFetchError {\n            switch fetchError {\n            case .unauthorized, .forbidden:\n                return true\n            default:\n                return false\n            }\n        }\n        return false\n    }\n\n    private static func mapUsage(\n        _ response: VertexAIUsageResponse?,\n        credentials: VertexAIOAuthCredentials) -> UsageSnapshot\n    {\n        // Token cost is fetched separately via CostUsageScanner from local Claude logs.\n        // Quota usage from Cloud Monitoring is optional - we still show token costs if unavailable.\n\n        let identity = ProviderIdentitySnapshot(\n            providerID: .vertexai,\n            accountEmail: credentials.email,\n            accountOrganization: credentials.projectId,\n            loginMethod: \"gcloud\")\n\n        return UsageSnapshot(\n            primary: nil,\n            secondary: nil,\n            providerCost: nil,\n            updatedAt: Date(),\n            identity: identity)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Warp/WarpProviderDescriptor.swift",
    "content": "import CodexBarMacroSupport\nimport Foundation\n\n@ProviderDescriptorRegistration\n@ProviderDescriptorDefinition\npublic enum WarpProviderDescriptor {\n    static func makeDescriptor() -> ProviderDescriptor {\n        ProviderDescriptor(\n            id: .warp,\n            metadata: ProviderMetadata(\n                id: .warp,\n                displayName: \"Warp\",\n                sessionLabel: \"Credits\",\n                weeklyLabel: \"Add-on credits\",\n                opusLabel: nil,\n                supportsOpus: false,\n                supportsCredits: false,\n                creditsHint: \"\",\n                toggleTitle: \"Show Warp usage\",\n                cliName: \"warp\",\n                defaultEnabled: false,\n                isPrimaryProvider: false,\n                usesAccountFallback: false,\n                browserCookieOrder: nil,\n                dashboardURL: \"https://docs.warp.dev/reference/cli/api-keys\",\n                statusPageURL: nil),\n            branding: ProviderBranding(\n                iconStyle: .warp,\n                iconResourceName: \"ProviderIcon-warp\",\n                color: ProviderColor(red: 147 / 255, green: 139 / 255, blue: 180 / 255)),\n            tokenCost: ProviderTokenCostConfig(\n                supportsTokenCost: false,\n                noDataMessage: { \"Warp cost summary is not available.\" }),\n            fetchPlan: ProviderFetchPlan(\n                sourceModes: [.auto, .api],\n                pipeline: ProviderFetchPipeline(resolveStrategies: { _ in [WarpAPIFetchStrategy()] })),\n            cli: ProviderCLIConfig(\n                name: \"warp\",\n                aliases: [\"warp-ai\", \"warp-terminal\"],\n                versionDetector: nil))\n    }\n}\n\nstruct WarpAPIFetchStrategy: ProviderFetchStrategy {\n    let id: String = \"warp.api\"\n    let kind: ProviderFetchKind = .apiToken\n\n    func isAvailable(_ context: ProviderFetchContext) async -> Bool {\n        Self.resolveToken(environment: context.env) != nil\n    }\n\n    func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {\n        guard let apiKey = Self.resolveToken(environment: context.env) else {\n            throw WarpUsageError.missingCredentials\n        }\n        let usage = try await WarpUsageFetcher.fetchUsage(apiKey: apiKey)\n        return self.makeResult(\n            usage: usage.toUsageSnapshot(),\n            sourceLabel: \"api\")\n    }\n\n    func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool {\n        false\n    }\n\n    private static func resolveToken(environment: [String: String]) -> String? {\n        ProviderTokenResolver.warpToken(environment: environment)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Warp/WarpSettingsReader.swift",
    "content": "import Foundation\n\npublic struct WarpSettingsReader: Sendable {\n    public static let apiKeyEnvironmentKeys = [\n        \"WARP_API_KEY\",\n        \"WARP_TOKEN\",\n    ]\n\n    public static func apiKey(\n        environment: [String: String] = ProcessInfo.processInfo.environment) -> String?\n    {\n        for key in self.apiKeyEnvironmentKeys {\n            guard let raw = environment[key]?.trimmingCharacters(in: .whitespacesAndNewlines),\n                  !raw.isEmpty\n            else {\n                continue\n            }\n            let cleaned = Self.cleaned(raw)\n            if !cleaned.isEmpty {\n                return cleaned\n            }\n        }\n        return nil\n    }\n\n    private static func cleaned(_ raw: String) -> String {\n        var value = raw\n        if (value.hasPrefix(\"\\\"\") && value.hasSuffix(\"\\\"\")) ||\n            (value.hasPrefix(\"'\") && value.hasSuffix(\"'\"))\n        {\n            value.removeFirst()\n            value.removeLast()\n        }\n        return value.trimmingCharacters(in: .whitespacesAndNewlines)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Warp/WarpUsageFetcher.swift",
    "content": "import Foundation\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n\npublic struct WarpUsageSnapshot: Sendable {\n    public let requestLimit: Int\n    public let requestsUsed: Int\n    public let nextRefreshTime: Date?\n    public let isUnlimited: Bool\n    public let updatedAt: Date\n    // Combined bonus credits (user-level + workspace-level)\n    public let bonusCreditsRemaining: Int\n    public let bonusCreditsTotal: Int\n    // Earliest expiring bonus batch with remaining credits\n    public let bonusNextExpiration: Date?\n    public let bonusNextExpirationRemaining: Int\n\n    public init(\n        requestLimit: Int,\n        requestsUsed: Int,\n        nextRefreshTime: Date?,\n        isUnlimited: Bool,\n        updatedAt: Date,\n        bonusCreditsRemaining: Int = 0,\n        bonusCreditsTotal: Int = 0,\n        bonusNextExpiration: Date? = nil,\n        bonusNextExpirationRemaining: Int = 0)\n    {\n        self.requestLimit = requestLimit\n        self.requestsUsed = requestsUsed\n        self.nextRefreshTime = nextRefreshTime\n        self.isUnlimited = isUnlimited\n        self.updatedAt = updatedAt\n        self.bonusCreditsRemaining = bonusCreditsRemaining\n        self.bonusCreditsTotal = bonusCreditsTotal\n        self.bonusNextExpiration = bonusNextExpiration\n        self.bonusNextExpirationRemaining = bonusNextExpirationRemaining\n    }\n\n    public func toUsageSnapshot() -> UsageSnapshot {\n        let usedPercent: Double = if self.isUnlimited {\n            0\n        } else if self.requestLimit > 0 {\n            min(100, max(0, Double(self.requestsUsed) / Double(self.requestLimit) * 100))\n        } else {\n            0\n        }\n\n        let resetDescription: String? = if self.isUnlimited {\n            \"Unlimited\"\n        } else {\n            \"\\(self.requestsUsed)/\\(self.requestLimit) credits\"\n        }\n\n        let primary = RateWindow(\n            usedPercent: usedPercent,\n            windowMinutes: nil,\n            resetsAt: self.isUnlimited ? nil : self.nextRefreshTime,\n            resetDescription: resetDescription)\n\n        // Secondary: combined bonus/add-on credits (user + workspace)\n        var bonusDetail: String?\n        if self.bonusCreditsRemaining > 0,\n           let expiry = self.bonusNextExpiration,\n           self.bonusNextExpirationRemaining > 0\n        {\n            let dateText = expiry.formatted(date: .abbreviated, time: .shortened)\n            bonusDetail = \"\\(self.bonusNextExpirationRemaining) credits expires on \\(dateText)\"\n        }\n\n        let hasBonusWindow = self.bonusCreditsTotal > 0\n            || self.bonusCreditsRemaining > 0\n            || (bonusDetail?.isEmpty == false)\n\n        let secondary: RateWindow?\n        if hasBonusWindow {\n            let bonusUsedPercent: Double = {\n                guard self.bonusCreditsTotal > 0 else {\n                    return self.bonusCreditsRemaining > 0 ? 0 : 100\n                }\n                let used = self.bonusCreditsTotal - self.bonusCreditsRemaining\n                return min(100, max(0, Double(used) / Double(self.bonusCreditsTotal) * 100))\n            }()\n            secondary = RateWindow(\n                usedPercent: bonusUsedPercent,\n                windowMinutes: nil,\n                resetsAt: nil,\n                resetDescription: bonusDetail)\n        } else {\n            secondary = nil\n        }\n\n        let identity = ProviderIdentitySnapshot(\n            providerID: .warp,\n            accountEmail: nil,\n            accountOrganization: nil,\n            loginMethod: nil)\n\n        return UsageSnapshot(\n            primary: primary,\n            secondary: secondary,\n            tertiary: nil,\n            providerCost: nil,\n            updatedAt: self.updatedAt,\n            identity: identity)\n    }\n}\n\npublic enum WarpUsageError: LocalizedError, Sendable {\n    case missingCredentials\n    case networkError(String)\n    case apiError(Int, String)\n    case parseFailed(String)\n\n    public var errorDescription: String? {\n        switch self {\n        case .missingCredentials:\n            \"Missing Warp API key.\"\n        case let .networkError(message):\n            \"Warp network error: \\(message)\"\n        case let .apiError(code, message):\n            \"Warp API error (\\(code)): \\(message)\"\n        case let .parseFailed(message):\n            \"Failed to parse Warp response: \\(message)\"\n        }\n    }\n}\n\npublic struct WarpUsageFetcher: Sendable {\n    private static let log = CodexBarLog.logger(LogCategories.warpUsage)\n    private static let apiURL = URL(string: \"https://app.warp.dev/graphql/v2?op=GetRequestLimitInfo\")!\n    private static let clientID = \"warp-app\"\n    /// Warp's GraphQL endpoint is fronted by an edge limiter that returns HTTP 429 (\"Rate exceeded.\")\n    /// unless the User-Agent matches the official client pattern (e.g. \"Warp/1.0\").\n    private static let userAgent = \"Warp/1.0\"\n\n    private static let graphQLQuery = \"\"\"\n    query GetRequestLimitInfo($requestContext: RequestContext!) {\n      user(requestContext: $requestContext) {\n        __typename\n        ... on UserOutput {\n          user {\n            requestLimitInfo {\n              isUnlimited\n              nextRefreshTime\n              requestLimit\n              requestsUsedSinceLastRefresh\n            }\n            bonusGrants {\n              requestCreditsGranted\n              requestCreditsRemaining\n              expiration\n            }\n            workspaces {\n              bonusGrantsInfo {\n                grants {\n                  requestCreditsGranted\n                  requestCreditsRemaining\n                  expiration\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n    \"\"\"\n\n    public static func fetchUsage(apiKey: String) async throws -> WarpUsageSnapshot {\n        guard !apiKey.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {\n            throw WarpUsageError.missingCredentials\n        }\n\n        let osVersion = ProcessInfo.processInfo.operatingSystemVersion\n        let osVersionString = \"\\(osVersion.majorVersion).\\(osVersion.minorVersion).\\(osVersion.patchVersion)\"\n\n        var request = URLRequest(url: self.apiURL)\n        request.httpMethod = \"POST\"\n        request.timeoutInterval = 15\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Content-Type\")\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Accept\")\n        request.setValue(self.clientID, forHTTPHeaderField: \"x-warp-client-id\")\n        request.setValue(\"macOS\", forHTTPHeaderField: \"x-warp-os-category\")\n        request.setValue(\"macOS\", forHTTPHeaderField: \"x-warp-os-name\")\n        request.setValue(osVersionString, forHTTPHeaderField: \"x-warp-os-version\")\n        request.setValue(\"Bearer \\(apiKey)\", forHTTPHeaderField: \"Authorization\")\n        request.setValue(self.userAgent, forHTTPHeaderField: \"User-Agent\")\n\n        let variables: [String: Any] = [\n            \"requestContext\": [\n                \"clientContext\": [:] as [String: Any],\n                \"osContext\": [\n                    \"category\": \"macOS\",\n                    \"name\": \"macOS\",\n                    \"version\": osVersionString,\n                ] as [String: Any],\n            ] as [String: Any],\n        ]\n\n        let body: [String: Any] = [\n            \"query\": self.graphQLQuery,\n            \"variables\": variables,\n            \"operationName\": \"GetRequestLimitInfo\",\n        ]\n\n        request.httpBody = try JSONSerialization.data(withJSONObject: body)\n\n        let (data, response) = try await URLSession.shared.data(for: request)\n\n        guard let httpResponse = response as? HTTPURLResponse else {\n            throw WarpUsageError.networkError(\"Invalid response\")\n        }\n\n        guard httpResponse.statusCode == 200 else {\n            let summary = Self.apiErrorSummary(statusCode: httpResponse.statusCode, data: data)\n            Self.log.error(\"Warp API returned \\(httpResponse.statusCode): \\(summary)\")\n            throw WarpUsageError.apiError(httpResponse.statusCode, summary)\n        }\n\n        do {\n            let snapshot = try Self.parseResponse(data: data)\n            Self.log.debug(\n                \"Warp usage parsed requestLimit=\\(snapshot.requestLimit) requestsUsed=\\(snapshot.requestsUsed) \"\n                    + \"bonusRemaining=\\(snapshot.bonusCreditsRemaining) bonusTotal=\\(snapshot.bonusCreditsTotal) \"\n                    + \"isUnlimited=\\(snapshot.isUnlimited)\")\n            return snapshot\n        } catch {\n            Self.log.error(\"Warp response parse failed bytes=\\(data.count) error=\\(error.localizedDescription)\")\n            throw error\n        }\n    }\n\n    static func _parseResponseForTesting(_ data: Data) throws -> WarpUsageSnapshot {\n        try self.parseResponse(data: data)\n    }\n\n    static func _apiErrorSummaryForTesting(statusCode: Int, data: Data) -> String {\n        self.apiErrorSummary(statusCode: statusCode, data: data)\n    }\n\n    private static func parseResponse(data: Data) throws -> WarpUsageSnapshot {\n        guard let root = try? JSONSerialization.jsonObject(with: data),\n              let json = root as? [String: Any]\n        else {\n            throw WarpUsageError.parseFailed(\"Root JSON is not an object.\")\n        }\n\n        if let rawErrors = json[\"errors\"] as? [Any], !rawErrors.isEmpty {\n            let messages = rawErrors.compactMap(Self.graphQLErrorMessage(from:))\n            let summary = messages.isEmpty ? \"GraphQL request failed.\" : messages.prefix(3).joined(separator: \" | \")\n            throw WarpUsageError.apiError(200, summary)\n        }\n\n        guard let dataObj = json[\"data\"] as? [String: Any],\n              let userObj = dataObj[\"user\"] as? [String: Any]\n        else {\n            throw WarpUsageError.parseFailed(\"Missing data.user in response.\")\n        }\n\n        let typeName = (userObj[\"__typename\"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines)\n\n        guard let innerUserObj = userObj[\"user\"] as? [String: Any],\n              let limitInfo = innerUserObj[\"requestLimitInfo\"] as? [String: Any]\n        else {\n            if let typeName, !typeName.isEmpty, typeName != \"UserOutput\" {\n                throw WarpUsageError.parseFailed(\"Unexpected user type '\\(typeName)'.\")\n            }\n            throw WarpUsageError.parseFailed(\"Unable to extract requestLimitInfo from response.\")\n        }\n\n        let isUnlimited = Self.boolValue(limitInfo[\"isUnlimited\"])\n        let requestLimit = self.intValue(limitInfo[\"requestLimit\"])\n        let requestsUsed = self.intValue(limitInfo[\"requestsUsedSinceLastRefresh\"])\n\n        var nextRefreshTime: Date?\n        if let nextRefreshTimeString = limitInfo[\"nextRefreshTime\"] as? String {\n            nextRefreshTime = Self.parseDate(nextRefreshTimeString)\n        }\n\n        // Parse and combine bonus credits from user-level and workspace-level\n        let bonus = Self.parseBonusCredits(from: innerUserObj)\n\n        return WarpUsageSnapshot(\n            requestLimit: requestLimit,\n            requestsUsed: requestsUsed,\n            nextRefreshTime: nextRefreshTime,\n            isUnlimited: isUnlimited,\n            updatedAt: Date(),\n            bonusCreditsRemaining: bonus.remaining,\n            bonusCreditsTotal: bonus.total,\n            bonusNextExpiration: bonus.nextExpiration,\n            bonusNextExpirationRemaining: bonus.nextExpirationRemaining)\n    }\n\n    private struct BonusGrant {\n        let granted: Int\n        let remaining: Int\n        let expiration: Date?\n    }\n\n    private struct BonusSummary {\n        let remaining: Int\n        let total: Int\n        let nextExpiration: Date?\n        let nextExpirationRemaining: Int\n    }\n\n    private static func parseBonusCredits(from userObj: [String: Any]) -> BonusSummary {\n        var grants: [BonusGrant] = []\n\n        // User-level bonus grants\n        if let bonusGrants = userObj[\"bonusGrants\"] as? [[String: Any]] {\n            for grant in bonusGrants {\n                grants.append(Self.parseBonusGrant(from: grant))\n            }\n        }\n\n        // Workspace-level bonus grants\n        if let workspaces = userObj[\"workspaces\"] as? [[String: Any]] {\n            for workspace in workspaces {\n                if let bonusGrantsInfo = workspace[\"bonusGrantsInfo\"] as? [String: Any],\n                   let workspaceGrants = bonusGrantsInfo[\"grants\"] as? [[String: Any]]\n                {\n                    for grant in workspaceGrants {\n                        grants.append(Self.parseBonusGrant(from: grant))\n                    }\n                }\n            }\n        }\n\n        let totalRemaining = grants.reduce(0) { $0 + $1.remaining }\n        let totalGranted = grants.reduce(0) { $0 + $1.granted }\n\n        let expiring = grants.compactMap { grant -> (date: Date, remaining: Int)? in\n            guard grant.remaining > 0, let expiration = grant.expiration else { return nil }\n            return (expiration, grant.remaining)\n        }\n\n        let nextExpiration: Date?\n        let nextExpirationRemaining: Int\n        if let earliest = expiring.min(by: { $0.date < $1.date }) {\n            let earliestKey = Int(earliest.date.timeIntervalSince1970)\n            let remaining = expiring.reduce(0) { result, item in\n                let key = Int(item.date.timeIntervalSince1970)\n                return result + (key == earliestKey ? item.remaining : 0)\n            }\n            nextExpiration = earliest.date\n            nextExpirationRemaining = remaining\n        } else {\n            nextExpiration = nil\n            nextExpirationRemaining = 0\n        }\n\n        return BonusSummary(\n            remaining: totalRemaining,\n            total: totalGranted,\n            nextExpiration: nextExpiration,\n            nextExpirationRemaining: nextExpirationRemaining)\n    }\n\n    private static func parseBonusGrant(from grant: [String: Any]) -> BonusGrant {\n        let granted = self.intValue(grant[\"requestCreditsGranted\"])\n        let remaining = self.intValue(grant[\"requestCreditsRemaining\"])\n        let expiration = (grant[\"expiration\"] as? String).flatMap(Self.parseDate)\n        return BonusGrant(granted: granted, remaining: remaining, expiration: expiration)\n    }\n\n    private static func intValue(_ value: Any?) -> Int {\n        if let int = value as? Int { return int }\n        if let num = value as? NSNumber { return num.intValue }\n        if let text = value as? String, let int = Int(text) { return int }\n        return 0\n    }\n\n    private static func boolValue(_ value: Any?) -> Bool {\n        if let bool = value as? Bool {\n            return bool\n        }\n        if let number = value as? NSNumber {\n            return number.boolValue\n        }\n        if let text = value as? String {\n            let normalized = text.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()\n            if [\"true\", \"1\", \"yes\"].contains(normalized) {\n                return true\n            }\n            if [\"false\", \"0\", \"no\"].contains(normalized) {\n                return false\n            }\n        }\n        return false\n    }\n\n    private static func graphQLErrorMessage(from value: Any) -> String? {\n        if let message = value as? String {\n            let trimmed = message.trimmingCharacters(in: .whitespacesAndNewlines)\n            return trimmed.isEmpty ? nil : trimmed\n        }\n        if let dict = value as? [String: Any],\n           let message = dict[\"message\"] as? String\n        {\n            let trimmed = message.trimmingCharacters(in: .whitespacesAndNewlines)\n            return trimmed.isEmpty ? nil : trimmed\n        }\n        return nil\n    }\n\n    private static func apiErrorSummary(statusCode: Int, data: Data) -> String {\n        guard let root = try? JSONSerialization.jsonObject(with: data),\n              let json = root as? [String: Any]\n        else {\n            if let text = String(data: data, encoding: .utf8)?\n                .trimmingCharacters(in: .whitespacesAndNewlines),\n                !text.isEmpty\n            {\n                return self.compactSummaryText(text)\n            }\n            return \"Unexpected response body (\\(data.count) bytes).\"\n        }\n\n        if let rawErrors = json[\"errors\"] as? [Any], !rawErrors.isEmpty {\n            let messages = rawErrors.compactMap(Self.graphQLErrorMessage(from:))\n            let joined = messages.prefix(3).joined(separator: \" | \")\n            if !joined.isEmpty {\n                return self.compactSummaryText(joined)\n            }\n        }\n\n        if let error = json[\"error\"] as? String {\n            let trimmed = error.trimmingCharacters(in: .whitespacesAndNewlines)\n            if !trimmed.isEmpty {\n                return self.compactSummaryText(trimmed)\n            }\n        }\n\n        if let message = json[\"message\"] as? String {\n            let trimmed = message.trimmingCharacters(in: .whitespacesAndNewlines)\n            if !trimmed.isEmpty {\n                return self.compactSummaryText(trimmed)\n            }\n        }\n\n        return \"HTTP \\(statusCode) (\\(data.count) bytes).\"\n    }\n\n    private static func compactSummaryText(_ text: String, maxLength: Int = 200) -> String {\n        let collapsed = text\n            .components(separatedBy: .newlines)\n            .joined(separator: \" \")\n            .trimmingCharacters(in: .whitespacesAndNewlines)\n        if collapsed.count <= maxLength {\n            return collapsed\n        }\n        let limitIndex = collapsed.index(collapsed.startIndex, offsetBy: maxLength)\n        return \"\\(collapsed[..<limitIndex])...\"\n    }\n\n    private static func parseDate(_ dateString: String) -> Date? {\n        let formatter = ISO8601DateFormatter()\n        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]\n        if let date = formatter.date(from: dateString) {\n            return date\n        }\n        let fallback = ISO8601DateFormatter()\n        fallback.formatOptions = [.withInternetDateTime]\n        return fallback.date(from: dateString)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Zai/ZaiAPIRegion.swift",
    "content": "import Foundation\n\npublic enum ZaiAPIRegion: String, CaseIterable, Sendable {\n    case global\n    case bigmodelCN = \"bigmodel-cn\"\n\n    private static let quotaPath = \"api/monitor/usage/quota/limit\"\n\n    public var displayName: String {\n        switch self {\n        case .global:\n            \"Global (api.z.ai)\"\n        case .bigmodelCN:\n            \"BigModel CN (open.bigmodel.cn)\"\n        }\n    }\n\n    public var baseURLString: String {\n        switch self {\n        case .global:\n            \"https://api.z.ai\"\n        case .bigmodelCN:\n            \"https://open.bigmodel.cn\"\n        }\n    }\n\n    public var quotaLimitURL: URL {\n        URL(string: self.baseURLString)!.appendingPathComponent(Self.quotaPath)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Zai/ZaiProviderDescriptor.swift",
    "content": "import CodexBarMacroSupport\nimport Foundation\n\n@ProviderDescriptorRegistration\n@ProviderDescriptorDefinition\npublic enum ZaiProviderDescriptor {\n    static func makeDescriptor() -> ProviderDescriptor {\n        ProviderDescriptor(\n            id: .zai,\n            metadata: ProviderMetadata(\n                id: .zai,\n                displayName: \"z.ai\",\n                sessionLabel: \"Tokens\",\n                weeklyLabel: \"MCP\",\n                opusLabel: nil,\n                supportsOpus: false,\n                supportsCredits: false,\n                creditsHint: \"\",\n                toggleTitle: \"Show z.ai usage\",\n                cliName: \"zai\",\n                defaultEnabled: false,\n                isPrimaryProvider: false,\n                usesAccountFallback: false,\n                dashboardURL: \"https://z.ai/manage-apikey/subscription\",\n                statusPageURL: nil),\n            branding: ProviderBranding(\n                iconStyle: .zai,\n                iconResourceName: \"ProviderIcon-zai\",\n                color: ProviderColor(red: 232 / 255, green: 90 / 255, blue: 106 / 255)),\n            tokenCost: ProviderTokenCostConfig(\n                supportsTokenCost: false,\n                noDataMessage: { \"z.ai cost summary is not supported.\" }),\n            fetchPlan: ProviderFetchPlan(\n                sourceModes: [.auto, .api],\n                pipeline: ProviderFetchPipeline(resolveStrategies: { _ in [ZaiAPIFetchStrategy()] })),\n            cli: ProviderCLIConfig(\n                name: \"zai\",\n                aliases: [\"z.ai\"],\n                versionDetector: nil))\n    }\n}\n\nstruct ZaiAPIFetchStrategy: ProviderFetchStrategy {\n    let id: String = \"zai.api\"\n    let kind: ProviderFetchKind = .apiToken\n\n    func isAvailable(_ context: ProviderFetchContext) async -> Bool {\n        Self.resolveToken(environment: context.env) != nil\n    }\n\n    func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {\n        guard let apiKey = Self.resolveToken(environment: context.env) else {\n            throw ZaiSettingsError.missingToken\n        }\n        let region = context.settings?.zai?.apiRegion ?? .global\n        let usage = try await ZaiUsageFetcher.fetchUsage(\n            apiKey: apiKey,\n            region: region,\n            environment: context.env)\n        return self.makeResult(\n            usage: usage.toUsageSnapshot(),\n            sourceLabel: \"api\")\n    }\n\n    func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool {\n        false\n    }\n\n    private static func resolveToken(environment: [String: String]) -> String? {\n        ProviderTokenResolver.zaiToken(environment: environment)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Zai/ZaiSettingsReader.swift",
    "content": "import Foundation\n\npublic struct ZaiSettingsReader: Sendable {\n    private static let log = CodexBarLog.logger(LogCategories.zaiSettings)\n\n    public static let apiTokenKey = \"Z_AI_API_KEY\"\n    public static let apiHostKey = \"Z_AI_API_HOST\"\n    public static let quotaURLKey = \"Z_AI_QUOTA_URL\"\n\n    public static func apiToken(\n        environment: [String: String] = ProcessInfo.processInfo.environment) -> String?\n    {\n        if let token = self.cleaned(environment[apiTokenKey]) { return token }\n        return nil\n    }\n\n    public static func apiHost(\n        environment: [String: String] = ProcessInfo.processInfo.environment) -> String?\n    {\n        self.cleaned(environment[self.apiHostKey])\n    }\n\n    public static func quotaURL(\n        environment: [String: String] = ProcessInfo.processInfo.environment) -> URL?\n    {\n        guard let raw = self.cleaned(environment[quotaURLKey]) else { return nil }\n        if let url = URL(string: raw), url.scheme != nil {\n            return url\n        }\n        return URL(string: \"https://\\(raw)\")\n    }\n\n    static func cleaned(_ raw: String?) -> String? {\n        guard var value = raw?.trimmingCharacters(in: .whitespacesAndNewlines), !value.isEmpty else {\n            return nil\n        }\n\n        if (value.hasPrefix(\"\\\"\") && value.hasSuffix(\"\\\"\")) ||\n            (value.hasPrefix(\"'\") && value.hasSuffix(\"'\"))\n        {\n            value.removeFirst()\n            value.removeLast()\n        }\n\n        value = value.trimmingCharacters(in: .whitespacesAndNewlines)\n        return value.isEmpty ? nil : value\n    }\n}\n\npublic enum ZaiSettingsError: LocalizedError, Sendable {\n    case missingToken\n\n    public var errorDescription: String? {\n        switch self {\n        case .missingToken:\n            \"z.ai API token not found. Set apiKey in ~/.codexbar/config.json or Z_AI_API_KEY.\"\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Providers/Zai/ZaiUsageStats.swift",
    "content": "import Foundation\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n\n/// Z.ai usage limit types from the API\npublic enum ZaiLimitType: String, Sendable {\n    case timeLimit = \"TIME_LIMIT\"\n    case tokensLimit = \"TOKENS_LIMIT\"\n}\n\n/// Z.ai usage limit unit types\npublic enum ZaiLimitUnit: Int, Sendable {\n    case unknown = 0\n    case days = 1\n    case hours = 3\n    case minutes = 5\n}\n\n/// A single limit entry from the z.ai API\npublic struct ZaiLimitEntry: Sendable {\n    public let type: ZaiLimitType\n    public let unit: ZaiLimitUnit\n    public let number: Int\n    public let usage: Int?\n    public let currentValue: Int?\n    public let remaining: Int?\n    public let percentage: Double\n    public let usageDetails: [ZaiUsageDetail]\n    public let nextResetTime: Date?\n\n    public init(\n        type: ZaiLimitType,\n        unit: ZaiLimitUnit,\n        number: Int,\n        usage: Int?,\n        currentValue: Int?,\n        remaining: Int?,\n        percentage: Double,\n        usageDetails: [ZaiUsageDetail],\n        nextResetTime: Date?)\n    {\n        self.type = type\n        self.unit = unit\n        self.number = number\n        self.usage = usage\n        self.currentValue = currentValue\n        self.remaining = remaining\n        self.percentage = percentage\n        self.usageDetails = usageDetails\n        self.nextResetTime = nextResetTime\n    }\n}\n\nextension ZaiLimitEntry {\n    public var usedPercent: Double {\n        if let computed = self.computedUsedPercent {\n            return computed\n        }\n        return self.percentage\n    }\n\n    public var windowMinutes: Int? {\n        guard self.number > 0 else { return nil }\n        switch self.unit {\n        case .minutes:\n            return self.number\n        case .hours:\n            return self.number * 60\n        case .days:\n            return self.number * 24 * 60\n        case .unknown:\n            return nil\n        }\n    }\n\n    public var windowDescription: String? {\n        guard self.number > 0 else { return nil }\n        let unitLabel: String? = switch self.unit {\n        case .minutes: \"minute\"\n        case .hours: \"hour\"\n        case .days: \"day\"\n        case .unknown: nil\n        }\n        guard let unitLabel else { return nil }\n        let suffix = self.number == 1 ? unitLabel : \"\\(unitLabel)s\"\n        return \"\\(self.number) \\(suffix)\"\n    }\n\n    public var windowLabel: String? {\n        guard let description = self.windowDescription else { return nil }\n        return \"\\(description) window\"\n    }\n\n    private var computedUsedPercent: Double? {\n        guard let limit = self.usage, limit > 0 else { return nil }\n\n        // z.ai sometimes omits quota fields; don't invent zeros (can yield 100% used incorrectly).\n        var usedRaw: Int?\n        if let remaining = self.remaining {\n            let usedFromRemaining = limit - remaining\n            if let currentValue = self.currentValue {\n                usedRaw = max(usedFromRemaining, currentValue)\n            } else {\n                usedRaw = usedFromRemaining\n            }\n        } else if let currentValue = self.currentValue {\n            usedRaw = currentValue\n        }\n        guard let usedRaw else { return nil }\n\n        let used = max(0, min(limit, usedRaw))\n        let percent = (Double(used) / Double(limit)) * 100\n        return min(100, max(0, percent))\n    }\n}\n\n/// Usage detail for MCP tools\npublic struct ZaiUsageDetail: Sendable, Codable {\n    public let modelCode: String\n    public let usage: Int\n\n    public init(modelCode: String, usage: Int) {\n        self.modelCode = modelCode\n        self.usage = usage\n    }\n}\n\n/// Complete z.ai usage response\npublic struct ZaiUsageSnapshot: Sendable {\n    public let tokenLimit: ZaiLimitEntry?\n    public let timeLimit: ZaiLimitEntry?\n    public let planName: String?\n    public let updatedAt: Date\n\n    public init(tokenLimit: ZaiLimitEntry?, timeLimit: ZaiLimitEntry?, planName: String?, updatedAt: Date) {\n        self.tokenLimit = tokenLimit\n        self.timeLimit = timeLimit\n        self.planName = planName\n        self.updatedAt = updatedAt\n    }\n\n    /// Returns true if this snapshot contains valid z.ai data\n    public var isValid: Bool {\n        self.tokenLimit != nil || self.timeLimit != nil\n    }\n}\n\nextension ZaiUsageSnapshot {\n    public func toUsageSnapshot() -> UsageSnapshot {\n        let primaryLimit = self.tokenLimit ?? self.timeLimit\n        let secondaryLimit = (self.tokenLimit != nil && self.timeLimit != nil) ? self.timeLimit : nil\n\n        let primary = primaryLimit.map { Self.rateWindow(for: $0) } ?? RateWindow(\n            usedPercent: 0,\n            windowMinutes: nil,\n            resetsAt: nil,\n            resetDescription: nil)\n        let secondary = secondaryLimit.map { Self.rateWindow(for: $0) }\n\n        let planName = self.planName?.trimmingCharacters(in: .whitespacesAndNewlines)\n        let loginMethod = (planName?.isEmpty ?? true) ? nil : planName\n        let identity = ProviderIdentitySnapshot(\n            providerID: .zai,\n            accountEmail: nil,\n            accountOrganization: nil,\n            loginMethod: loginMethod)\n        return UsageSnapshot(\n            primary: primary,\n            secondary: secondary,\n            tertiary: nil,\n            providerCost: nil,\n            zaiUsage: self,\n            updatedAt: self.updatedAt,\n            identity: identity)\n    }\n\n    private static func rateWindow(for limit: ZaiLimitEntry) -> RateWindow {\n        RateWindow(\n            usedPercent: limit.usedPercent,\n            windowMinutes: limit.type == .tokensLimit ? limit.windowMinutes : nil,\n            resetsAt: limit.nextResetTime,\n            resetDescription: self.resetDescription(for: limit))\n    }\n\n    private static func resetDescription(for limit: ZaiLimitEntry) -> String? {\n        if let label = limit.windowLabel {\n            return label\n        }\n        if limit.type == .timeLimit {\n            return \"Monthly\"\n        }\n        return nil\n    }\n}\n\n/// Z.ai quota limit API response\nprivate struct ZaiQuotaLimitResponse: Decodable {\n    let code: Int\n    let msg: String\n    let data: ZaiQuotaLimitData?\n    let success: Bool\n\n    var isSuccess: Bool {\n        self.success && self.code == 200\n    }\n}\n\nprivate struct ZaiQuotaLimitData: Decodable {\n    let limits: [ZaiLimitRaw]\n    let planName: String?\n\n    init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        self.limits = try container.decodeIfPresent([ZaiLimitRaw].self, forKey: .limits) ?? []\n        let rawPlan = try [\n            container.decodeIfPresent(String.self, forKey: .planName),\n            container.decodeIfPresent(String.self, forKey: .plan),\n            container.decodeIfPresent(String.self, forKey: .planType),\n            container.decodeIfPresent(String.self, forKey: .packageName),\n        ].compactMap(\\.self).first\n        let trimmed = rawPlan?.trimmingCharacters(in: .whitespacesAndNewlines)\n        self.planName = (trimmed?.isEmpty ?? true) ? nil : trimmed\n    }\n\n    private enum CodingKeys: String, CodingKey {\n        case limits\n        case planName\n        case plan\n        case planType = \"plan_type\"\n        case packageName\n    }\n}\n\nprivate struct ZaiLimitRaw: Codable {\n    let type: String\n    let unit: Int\n    let number: Int\n    let usage: Int?\n    let currentValue: Int?\n    let remaining: Int?\n    let percentage: Int\n    let usageDetails: [ZaiUsageDetail]?\n    let nextResetTime: Int?\n\n    func toLimitEntry() -> ZaiLimitEntry? {\n        guard let limitType = ZaiLimitType(rawValue: type) else { return nil }\n        let limitUnit = ZaiLimitUnit(rawValue: unit) ?? .unknown\n        let nextReset = self.nextResetTime.map { Date(timeIntervalSince1970: TimeInterval($0) / 1000) }\n        return ZaiLimitEntry(\n            type: limitType,\n            unit: limitUnit,\n            number: self.number,\n            usage: self.usage,\n            currentValue: self.currentValue,\n            remaining: self.remaining,\n            percentage: Double(self.percentage),\n            usageDetails: self.usageDetails ?? [],\n            nextResetTime: nextReset)\n    }\n}\n\n/// Fetches usage stats from the z.ai API\npublic struct ZaiUsageFetcher: Sendable {\n    private static let log = CodexBarLog.logger(LogCategories.zaiUsage)\n\n    /// Path for z.ai quota API\n    private static let quotaAPIPath = \"api/monitor/usage/quota/limit\"\n\n    /// Resolves the quota URL using (in order):\n    /// 1) `Z_AI_QUOTA_URL` environment override (full URL).\n    /// 2) `Z_AI_API_HOST` environment override (host/base URL).\n    /// 3) Region selection (global default).\n    public static func resolveQuotaURL(\n        region: ZaiAPIRegion,\n        environment: [String: String] = ProcessInfo.processInfo.environment) -> URL\n    {\n        if let override = ZaiSettingsReader.quotaURL(environment: environment) {\n            return override\n        }\n        if let host = ZaiSettingsReader.apiHost(environment: environment),\n           let hostURL = self.quotaURL(baseURLString: host)\n        {\n            return hostURL\n        }\n        return region.quotaLimitURL\n    }\n\n    /// Fetches usage stats from z.ai using the provided API key\n    public static func fetchUsage(\n        apiKey: String,\n        region: ZaiAPIRegion = .global,\n        environment: [String: String] = ProcessInfo.processInfo.environment) async throws -> ZaiUsageSnapshot\n    {\n        guard !apiKey.isEmpty else {\n            throw ZaiUsageError.invalidCredentials\n        }\n\n        let quotaURL = self.resolveQuotaURL(region: region, environment: environment)\n\n        var request = URLRequest(url: quotaURL)\n        request.httpMethod = \"GET\"\n        request.setValue(\"Bearer \\(apiKey)\", forHTTPHeaderField: \"authorization\")\n        request.setValue(\"application/json\", forHTTPHeaderField: \"accept\")\n\n        let (data, response) = try await URLSession.shared.data(for: request)\n\n        guard let httpResponse = response as? HTTPURLResponse else {\n            throw ZaiUsageError.networkError(\"Invalid response\")\n        }\n\n        guard httpResponse.statusCode == 200 else {\n            let errorMessage = String(data: data, encoding: .utf8) ?? \"Unknown error\"\n            Self.log.error(\"z.ai API returned \\(httpResponse.statusCode): \\(errorMessage)\")\n            throw ZaiUsageError.apiError(\"HTTP \\(httpResponse.statusCode): \\(errorMessage)\")\n        }\n\n        // Some upstream issues (wrong endpoint/region/proxy) can yield HTTP 200 with an empty body.\n        // JSONDecoder will otherwise throw an opaque Cocoa error (\"data is missing\").\n        guard !data.isEmpty else {\n            Self.log.error(\"z.ai API returned empty body (HTTP 200) for \\(Self.safeURLForLogging(quotaURL))\")\n            throw ZaiUsageError.parseFailed(\n                \"Empty response body (HTTP 200). Check z.ai API region (Global vs BigModel CN) and your API token.\")\n        }\n\n        // Log raw response for debugging\n        if let jsonString = String(data: data, encoding: .utf8) {\n            Self.log.debug(\"z.ai API response: \\(jsonString)\")\n        }\n\n        do {\n            return try Self.parseUsageSnapshot(from: data)\n        } catch let error as DecodingError {\n            Self.log.error(\"z.ai JSON decoding error: \\(error.localizedDescription)\")\n            throw ZaiUsageError.parseFailed(error.localizedDescription)\n        } catch let error as ZaiUsageError {\n            throw error\n        } catch {\n            Self.log.error(\"z.ai parsing error: \\(error.localizedDescription)\")\n            throw ZaiUsageError.parseFailed(error.localizedDescription)\n        }\n    }\n\n    private static func safeURLForLogging(_ url: URL) -> String {\n        let host = url.host ?? \"<unknown-host>\"\n        let port = url.port.map { \":\\($0)\" } ?? \"\"\n        let path = url.path.isEmpty ? \"/\" : url.path\n        return \"\\(host)\\(port)\\(path)\"\n    }\n\n    static func parseUsageSnapshot(from data: Data) throws -> ZaiUsageSnapshot {\n        guard !data.isEmpty else {\n            throw ZaiUsageError.parseFailed(\"Empty response body\")\n        }\n\n        let decoder = JSONDecoder()\n        let apiResponse = try decoder.decode(ZaiQuotaLimitResponse.self, from: data)\n\n        guard apiResponse.isSuccess else {\n            throw ZaiUsageError.apiError(apiResponse.msg)\n        }\n\n        guard let responseData = apiResponse.data else {\n            throw ZaiUsageError.parseFailed(\"Missing data\")\n        }\n\n        var tokenLimit: ZaiLimitEntry?\n        var timeLimit: ZaiLimitEntry?\n\n        for limit in responseData.limits {\n            if let entry = limit.toLimitEntry() {\n                switch entry.type {\n                case .tokensLimit:\n                    tokenLimit = entry\n                case .timeLimit:\n                    timeLimit = entry\n                }\n            }\n        }\n\n        return ZaiUsageSnapshot(\n            tokenLimit: tokenLimit,\n            timeLimit: timeLimit,\n            planName: responseData.planName,\n            updatedAt: Date())\n    }\n\n    private static func quotaURL(baseURLString: String) -> URL? {\n        guard let cleaned = ZaiSettingsReader.cleaned(baseURLString) else { return nil }\n\n        if let url = URL(string: cleaned), url.scheme != nil {\n            if url.path.isEmpty || url.path == \"/\" {\n                return url.appendingPathComponent(Self.quotaAPIPath)\n            }\n            return url\n        }\n        guard let base = URL(string: \"https://\\(cleaned)\") else { return nil }\n        if base.path.isEmpty || base.path == \"/\" {\n            return base.appendingPathComponent(Self.quotaAPIPath)\n        }\n        return base\n    }\n}\n\n/// Errors that can occur during z.ai usage fetching\npublic enum ZaiUsageError: LocalizedError, Sendable {\n    case invalidCredentials\n    case networkError(String)\n    case apiError(String)\n    case parseFailed(String)\n\n    public var errorDescription: String? {\n        switch self {\n        case .invalidCredentials:\n            \"Invalid z.ai API credentials\"\n        case let .networkError(message):\n            \"z.ai network error: \\(message)\"\n        case let .apiError(message):\n            \"z.ai API error: \\(message)\"\n        case let .parseFailed(message):\n            \"Failed to parse z.ai response: \\(message)\"\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/TextParsing.swift",
    "content": "import Foundation\n\npublic enum TextParsing {\n    /// Removes ANSI escape sequences so regex parsing works on colored terminal output.\n    public static func stripANSICodes(_ text: String) -> String {\n        // CSI sequences: ESC [ ... ending in 0x40–0x7E\n        let pattern = #\"\\u001B\\[[0-?]*[ -/]*[@-~]\"#\n        guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { return text }\n        let range = NSRange(text.startIndex..<text.endIndex, in: text)\n        return regex.stringByReplacingMatches(in: text, options: [], range: range, withTemplate: \"\")\n    }\n\n    public static func firstNumber(pattern: String, text: String) -> Double? {\n        guard let regex = try? NSRegularExpression(pattern: pattern, options: [.caseInsensitive]) else { return nil }\n        let range = NSRange(text.startIndex..<text.endIndex, in: text)\n        guard let match = regex.firstMatch(in: text, options: [], range: range),\n              match.numberOfRanges >= 2,\n              let r = Range(match.range(at: 1), in: text) else { return nil }\n        let raw = String(text[r])\n        return Self.parseNumber(raw)\n    }\n\n    private static func parseNumber(_ raw: String) -> Double? {\n        var text = raw.trimmingCharacters(in: .whitespacesAndNewlines)\n        text = text.replacingOccurrences(of: \"\\u{00A0}\", with: \"\")\n        text = text.replacingOccurrences(of: \"\\u{202F}\", with: \"\")\n        text = text.replacingOccurrences(of: \" \", with: \"\")\n\n        let hasComma = text.contains(\",\")\n        let hasDot = text.contains(\".\")\n\n        if hasComma, hasDot {\n            if let lastComma = text.lastIndex(of: \",\"), let lastDot = text.lastIndex(of: \".\") {\n                if lastComma > lastDot {\n                    text = text.replacingOccurrences(of: \".\", with: \"\")\n                    text = text.replacingOccurrences(of: \",\", with: \".\")\n                } else {\n                    text = text.replacingOccurrences(of: \",\", with: \"\")\n                }\n            }\n        } else if hasComma {\n            if text.range(of: #\"^\\d{1,3}(,\\d{3})+$\"#, options: .regularExpression) != nil {\n                text = text.replacingOccurrences(of: \",\", with: \"\")\n            } else {\n                text = text.replacingOccurrences(of: \",\", with: \".\")\n            }\n        } else if hasDot {\n            if text.range(of: #\"^\\d{1,3}(\\.\\d{3})+$\"#, options: .regularExpression) != nil {\n                text = text.replacingOccurrences(of: \".\", with: \"\")\n            }\n        }\n\n        return Double(text)\n    }\n\n    public static func firstInt(pattern: String, text: String) -> Int? {\n        guard let v = firstNumber(pattern: pattern, text: text) else { return nil }\n        return Int(v)\n    }\n\n    public static func firstLine(matching pattern: String, text: String) -> String? {\n        guard let regex = try? NSRegularExpression(pattern: pattern, options: [.caseInsensitive]) else { return nil }\n        let range = NSRange(text.startIndex..<text.endIndex, in: text)\n        guard let match = regex.firstMatch(in: text, options: [], range: range),\n              let r = Range(match.range(at: 0), in: text) else { return nil }\n        return String(text[r])\n    }\n\n    public static func percentLeft(fromLine line: String) -> Int? {\n        guard let pct = firstInt(pattern: #\"([0-9]{1,3})%\\s+left\"#, text: line) else { return nil }\n        return pct\n    }\n\n    public static func resetString(fromLine line: String) -> String? {\n        guard let regex = try? NSRegularExpression(pattern: #\"resets?\\s+(.+)\"#, options: [.caseInsensitive]) else {\n            return nil\n        }\n        let range = NSRange(line.startIndex..<line.endIndex, in: line)\n        guard let match = regex.firstMatch(in: line, options: [], range: range),\n              match.numberOfRanges >= 2,\n              let r = Range(match.range(at: 1), in: line)\n        else {\n            return nil\n        }\n        // Return the tail text only (drop the \"resets\" prefix).\n        return String(line[r]).trimmingCharacters(in: .whitespacesAndNewlines)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/TokenAccountSupport.swift",
    "content": "import Foundation\n\npublic enum TokenAccountInjection: Sendable {\n    case cookieHeader\n    case environment(key: String)\n}\n\npublic struct TokenAccountSupport: Sendable {\n    public let title: String\n    public let subtitle: String\n    public let placeholder: String\n    public let injection: TokenAccountInjection\n    public let requiresManualCookieSource: Bool\n    public let cookieName: String?\n\n    public init(\n        title: String,\n        subtitle: String,\n        placeholder: String,\n        injection: TokenAccountInjection,\n        requiresManualCookieSource: Bool,\n        cookieName: String?)\n    {\n        self.title = title\n        self.subtitle = subtitle\n        self.placeholder = placeholder\n        self.injection = injection\n        self.requiresManualCookieSource = requiresManualCookieSource\n        self.cookieName = cookieName\n    }\n}\n\npublic enum TokenAccountSupportCatalog {\n    public static func support(for provider: UsageProvider) -> TokenAccountSupport? {\n        supportByProvider[provider]\n    }\n\n    public static func envOverride(for provider: UsageProvider, token: String) -> [String: String]? {\n        guard let support = self.support(for: provider) else { return nil }\n        switch support.injection {\n        case let .environment(key):\n            return [key: token]\n        case .cookieHeader:\n            if provider == .claude,\n               case let .oauth(accessToken) = ClaudeCredentialRouting.resolve(\n                   tokenAccountToken: token,\n                   manualCookieHeader: nil)\n            {\n                return [ClaudeOAuthCredentialsStore.environmentTokenKey: accessToken]\n            }\n            return nil\n        }\n    }\n\n    public static func normalizedCookieHeader(for provider: UsageProvider, token: String) -> String {\n        guard let support = self.support(for: provider) else {\n            return token.trimmingCharacters(in: .whitespacesAndNewlines)\n        }\n        return self.normalizedCookieHeader(token, support: support)\n    }\n\n    public static func isClaudeOAuthToken(_ token: String) -> Bool {\n        ClaudeCredentialRouting.resolve(tokenAccountToken: token, manualCookieHeader: nil).isOAuth\n    }\n\n    public static func normalizedCookieHeader(_ token: String, support: TokenAccountSupport) -> String {\n        let trimmed = token.trimmingCharacters(in: .whitespacesAndNewlines)\n        guard let cookieName = support.cookieName else { return trimmed }\n        let lower = trimmed.lowercased()\n        if lower.contains(\"cookie:\") || trimmed.contains(\"=\") {\n            return trimmed\n        }\n        return \"\\(cookieName)=\\(trimmed)\"\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/TokenAccountSupportCatalog+Data.swift",
    "content": "import Foundation\n\nextension TokenAccountSupportCatalog {\n    static let supportByProvider: [UsageProvider: TokenAccountSupport] = [\n        .claude: TokenAccountSupport(\n            title: \"Session tokens\",\n            subtitle: \"Store Claude sessionKey cookies or OAuth access tokens.\",\n            placeholder: \"Paste sessionKey or OAuth token…\",\n            injection: .cookieHeader,\n            requiresManualCookieSource: true,\n            cookieName: \"sessionKey\"),\n        .zai: TokenAccountSupport(\n            title: \"API tokens\",\n            subtitle: \"Stored in the CodexBar config file.\",\n            placeholder: \"Paste token…\",\n            injection: .environment(key: ZaiSettingsReader.apiTokenKey),\n            requiresManualCookieSource: false,\n            cookieName: nil),\n        .cursor: TokenAccountSupport(\n            title: \"Session tokens\",\n            subtitle: \"Store multiple Cursor Cookie headers.\",\n            placeholder: \"Cookie: …\",\n            injection: .cookieHeader,\n            requiresManualCookieSource: true,\n            cookieName: nil),\n        .opencode: TokenAccountSupport(\n            title: \"Session tokens\",\n            subtitle: \"Store multiple OpenCode Cookie headers.\",\n            placeholder: \"Cookie: …\",\n            injection: .cookieHeader,\n            requiresManualCookieSource: true,\n            cookieName: nil),\n        .factory: TokenAccountSupport(\n            title: \"Session tokens\",\n            subtitle: \"Store multiple Factory Cookie headers.\",\n            placeholder: \"Cookie: …\",\n            injection: .cookieHeader,\n            requiresManualCookieSource: true,\n            cookieName: nil),\n        .minimax: TokenAccountSupport(\n            title: \"Session tokens\",\n            subtitle: \"Store multiple MiniMax Cookie headers.\",\n            placeholder: \"Cookie: …\",\n            injection: .cookieHeader,\n            requiresManualCookieSource: true,\n            cookieName: nil),\n        .augment: TokenAccountSupport(\n            title: \"Session tokens\",\n            subtitle: \"Store multiple Augment Cookie headers.\",\n            placeholder: \"Cookie: …\",\n            injection: .cookieHeader,\n            requiresManualCookieSource: true,\n            cookieName: nil),\n        .ollama: TokenAccountSupport(\n            title: \"Session tokens\",\n            subtitle: \"Store multiple Ollama Cookie headers.\",\n            placeholder: \"Cookie: …\",\n            injection: .cookieHeader,\n            requiresManualCookieSource: true,\n            cookieName: nil),\n    ]\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/TokenAccounts.swift",
    "content": "import Foundation\n\npublic struct ProviderTokenAccount: Codable, Identifiable, Sendable {\n    public let id: UUID\n    public let label: String\n    public let token: String\n    public let addedAt: TimeInterval\n    public let lastUsed: TimeInterval?\n\n    public init(id: UUID, label: String, token: String, addedAt: TimeInterval, lastUsed: TimeInterval?) {\n        self.id = id\n        self.label = label\n        self.token = token\n        self.addedAt = addedAt\n        self.lastUsed = lastUsed\n    }\n\n    public var displayName: String {\n        self.label\n    }\n}\n\npublic struct ProviderTokenAccountData: Codable, Sendable {\n    public let version: Int\n    public let accounts: [ProviderTokenAccount]\n    public let activeIndex: Int\n\n    public init(version: Int, accounts: [ProviderTokenAccount], activeIndex: Int) {\n        self.version = version\n        self.accounts = accounts\n        self.activeIndex = activeIndex\n    }\n\n    public func clampedActiveIndex() -> Int {\n        guard !self.accounts.isEmpty else { return 0 }\n        return min(max(self.activeIndex, 0), self.accounts.count - 1)\n    }\n}\n\nprivate struct ProviderTokenAccountsFile: Codable {\n    let version: Int\n    let providers: [String: ProviderTokenAccountData]\n}\n\npublic protocol ProviderTokenAccountStoring: Sendable {\n    func loadAccounts() throws -> [UsageProvider: ProviderTokenAccountData]\n    func storeAccounts(_ accounts: [UsageProvider: ProviderTokenAccountData]) throws\n    func ensureFileExists() throws -> URL\n}\n\npublic struct FileTokenAccountStore: ProviderTokenAccountStoring, @unchecked Sendable {\n    private let fileURL: URL\n    private let fileManager: FileManager\n\n    public init(fileURL: URL = Self.defaultURL(), fileManager: FileManager = .default) {\n        self.fileURL = fileURL\n        self.fileManager = fileManager\n    }\n\n    public func loadAccounts() throws -> [UsageProvider: ProviderTokenAccountData] {\n        guard self.fileManager.fileExists(atPath: self.fileURL.path) else { return [:] }\n        let data = try Data(contentsOf: self.fileURL)\n        let decoder = JSONDecoder()\n        let decoded = try decoder.decode(ProviderTokenAccountsFile.self, from: data)\n        var result: [UsageProvider: ProviderTokenAccountData] = [:]\n        for (key, value) in decoded.providers {\n            guard let provider = UsageProvider(rawValue: key) else { continue }\n            result[provider] = value\n        }\n        return result\n    }\n\n    public func storeAccounts(_ accounts: [UsageProvider: ProviderTokenAccountData]) throws {\n        let payload = ProviderTokenAccountsFile(\n            version: 1,\n            providers: Dictionary(uniqueKeysWithValues: accounts.map { ($0.key.rawValue, $0.value) }))\n        let encoder = JSONEncoder()\n        encoder.outputFormatting = [.prettyPrinted, .sortedKeys]\n        let data = try encoder.encode(payload)\n        let directory = self.fileURL.deletingLastPathComponent()\n        if !self.fileManager.fileExists(atPath: directory.path) {\n            try self.fileManager.createDirectory(at: directory, withIntermediateDirectories: true)\n        }\n        try data.write(to: self.fileURL, options: [.atomic])\n        try self.applySecurePermissionsIfNeeded()\n    }\n\n    public func ensureFileExists() throws -> URL {\n        if self.fileManager.fileExists(atPath: self.fileURL.path) { return self.fileURL }\n        try self.storeAccounts([:])\n        return self.fileURL\n    }\n\n    private func applySecurePermissionsIfNeeded() throws {\n        #if os(macOS)\n        try self.fileManager.setAttributes([\n            .posixPermissions: NSNumber(value: Int16(0o600)),\n        ], ofItemAtPath: self.fileURL.path)\n        #endif\n    }\n\n    public static func defaultURL() -> URL {\n        let base = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first\n            ?? FileManager.default.homeDirectoryForCurrentUser\n        return base\n            .appendingPathComponent(\"CodexBar\", isDirectory: true)\n            .appendingPathComponent(\"token-accounts.json\")\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/UsageFetcher.swift",
    "content": "import Foundation\n\npublic struct RateWindow: Codable, Equatable, Sendable {\n    public let usedPercent: Double\n    public let windowMinutes: Int?\n    public let resetsAt: Date?\n    /// Optional textual reset description (used by Claude CLI UI scrape).\n    public let resetDescription: String?\n\n    public init(usedPercent: Double, windowMinutes: Int?, resetsAt: Date?, resetDescription: String?) {\n        self.usedPercent = usedPercent\n        self.windowMinutes = windowMinutes\n        self.resetsAt = resetsAt\n        self.resetDescription = resetDescription\n    }\n\n    public var remainingPercent: Double {\n        max(0, 100 - self.usedPercent)\n    }\n}\n\npublic struct ProviderIdentitySnapshot: Codable, Sendable {\n    public let providerID: UsageProvider?\n    public let accountEmail: String?\n    public let accountOrganization: String?\n    public let loginMethod: String?\n\n    public init(\n        providerID: UsageProvider?,\n        accountEmail: String?,\n        accountOrganization: String?,\n        loginMethod: String?)\n    {\n        self.providerID = providerID\n        self.accountEmail = accountEmail\n        self.accountOrganization = accountOrganization\n        self.loginMethod = loginMethod\n    }\n\n    public func scoped(to provider: UsageProvider) -> ProviderIdentitySnapshot {\n        if self.providerID == provider { return self }\n        return ProviderIdentitySnapshot(\n            providerID: provider,\n            accountEmail: self.accountEmail,\n            accountOrganization: self.accountOrganization,\n            loginMethod: self.loginMethod)\n    }\n}\n\npublic struct UsageSnapshot: Codable, Sendable {\n    public let primary: RateWindow?\n    public let secondary: RateWindow?\n    public let tertiary: RateWindow?\n    public let providerCost: ProviderCostSnapshot?\n    public let zaiUsage: ZaiUsageSnapshot?\n    public let minimaxUsage: MiniMaxUsageSnapshot?\n    public let openRouterUsage: OpenRouterUsageSnapshot?\n    public let cursorRequests: CursorRequestUsage?\n    public let updatedAt: Date\n    public let identity: ProviderIdentitySnapshot?\n\n    private enum CodingKeys: String, CodingKey {\n        case primary\n        case secondary\n        case tertiary\n        case providerCost\n        case openRouterUsage\n        case updatedAt\n        case identity\n        case accountEmail\n        case accountOrganization\n        case loginMethod\n    }\n\n    public init(\n        primary: RateWindow?,\n        secondary: RateWindow?,\n        tertiary: RateWindow? = nil,\n        providerCost: ProviderCostSnapshot? = nil,\n        zaiUsage: ZaiUsageSnapshot? = nil,\n        minimaxUsage: MiniMaxUsageSnapshot? = nil,\n        openRouterUsage: OpenRouterUsageSnapshot? = nil,\n        cursorRequests: CursorRequestUsage? = nil,\n        updatedAt: Date,\n        identity: ProviderIdentitySnapshot? = nil)\n    {\n        self.primary = primary\n        self.secondary = secondary\n        self.tertiary = tertiary\n        self.providerCost = providerCost\n        self.zaiUsage = zaiUsage\n        self.minimaxUsage = minimaxUsage\n        self.openRouterUsage = openRouterUsage\n        self.cursorRequests = cursorRequests\n        self.updatedAt = updatedAt\n        self.identity = identity\n    }\n\n    public init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        self.primary = try container.decodeIfPresent(RateWindow.self, forKey: .primary)\n        self.secondary = try container.decodeIfPresent(RateWindow.self, forKey: .secondary)\n        self.tertiary = try container.decodeIfPresent(RateWindow.self, forKey: .tertiary)\n        self.providerCost = try container.decodeIfPresent(ProviderCostSnapshot.self, forKey: .providerCost)\n        self.zaiUsage = nil // Not persisted, fetched fresh each time\n        self.minimaxUsage = nil // Not persisted, fetched fresh each time\n        self.openRouterUsage = try container.decodeIfPresent(OpenRouterUsageSnapshot.self, forKey: .openRouterUsage)\n        self.cursorRequests = nil // Not persisted, fetched fresh each time\n        self.updatedAt = try container.decode(Date.self, forKey: .updatedAt)\n        if let identity = try container.decodeIfPresent(ProviderIdentitySnapshot.self, forKey: .identity) {\n            self.identity = identity\n        } else {\n            let email = try container.decodeIfPresent(String.self, forKey: .accountEmail)\n            let organization = try container.decodeIfPresent(String.self, forKey: .accountOrganization)\n            let loginMethod = try container.decodeIfPresent(String.self, forKey: .loginMethod)\n            if email != nil || organization != nil || loginMethod != nil {\n                self.identity = ProviderIdentitySnapshot(\n                    providerID: nil,\n                    accountEmail: email,\n                    accountOrganization: organization,\n                    loginMethod: loginMethod)\n            } else {\n                self.identity = nil\n            }\n        }\n    }\n\n    public func encode(to encoder: Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        // Stable JSON schema: keep window keys present (encode `nil` as `null`).\n        try container.encode(self.primary, forKey: .primary)\n        try container.encode(self.secondary, forKey: .secondary)\n        try container.encode(self.tertiary, forKey: .tertiary)\n        try container.encodeIfPresent(self.providerCost, forKey: .providerCost)\n        try container.encodeIfPresent(self.openRouterUsage, forKey: .openRouterUsage)\n        try container.encode(self.updatedAt, forKey: .updatedAt)\n        try container.encodeIfPresent(self.identity, forKey: .identity)\n        try container.encodeIfPresent(self.identity?.accountEmail, forKey: .accountEmail)\n        try container.encodeIfPresent(self.identity?.accountOrganization, forKey: .accountOrganization)\n        try container.encodeIfPresent(self.identity?.loginMethod, forKey: .loginMethod)\n    }\n\n    public func identity(for provider: UsageProvider) -> ProviderIdentitySnapshot? {\n        guard let identity, identity.providerID == provider else { return nil }\n        return identity\n    }\n\n    public func switcherWeeklyWindow(for provider: UsageProvider, showUsed: Bool) -> RateWindow? {\n        switch provider {\n        case .factory:\n            // Factory prefers secondary window\n            return self.secondary ?? self.primary\n        case .cursor:\n            // Cursor: fall back to On-Demand when Plan is exhausted (only in \"show remaining\" mode).\n            // In \"show used\" mode, keep showing primary so 100% used Plan is visible.\n            if !showUsed,\n               let primary = self.primary,\n               primary.remainingPercent <= 0,\n               let secondary = self.secondary\n            {\n                return secondary\n            }\n            return self.primary ?? self.secondary\n        default:\n            return self.primary ?? self.secondary\n        }\n    }\n\n    public func accountEmail(for provider: UsageProvider) -> String? {\n        self.identity(for: provider)?.accountEmail\n    }\n\n    public func accountOrganization(for provider: UsageProvider) -> String? {\n        self.identity(for: provider)?.accountOrganization\n    }\n\n    public func loginMethod(for provider: UsageProvider) -> String? {\n        self.identity(for: provider)?.loginMethod\n    }\n\n    /// Keep this initializer-style copy in sync with UsageSnapshot fields so relabeling/scoping never drops data.\n    public func withIdentity(_ identity: ProviderIdentitySnapshot?) -> UsageSnapshot {\n        UsageSnapshot(\n            primary: self.primary,\n            secondary: self.secondary,\n            tertiary: self.tertiary,\n            providerCost: self.providerCost,\n            zaiUsage: self.zaiUsage,\n            minimaxUsage: self.minimaxUsage,\n            openRouterUsage: self.openRouterUsage,\n            cursorRequests: self.cursorRequests,\n            updatedAt: self.updatedAt,\n            identity: identity)\n    }\n\n    public func scoped(to provider: UsageProvider) -> UsageSnapshot {\n        guard let identity else { return self }\n        let scopedIdentity = identity.scoped(to: provider)\n        if scopedIdentity.providerID == identity.providerID { return self }\n        return self.withIdentity(scopedIdentity)\n    }\n}\n\npublic struct AccountInfo: Equatable, Sendable {\n    public let email: String?\n    public let plan: String?\n\n    public init(email: String?, plan: String?) {\n        self.email = email\n        self.plan = plan\n    }\n}\n\npublic enum UsageError: LocalizedError, Sendable {\n    case noSessions\n    case noRateLimitsFound\n    case decodeFailed\n\n    public var errorDescription: String? {\n        switch self {\n        case .noSessions:\n            \"No Codex sessions found yet. Run at least one Codex prompt first.\"\n        case .noRateLimitsFound:\n            \"Found sessions, but no rate limit events yet.\"\n        case .decodeFailed:\n            \"Could not parse Codex session log.\"\n        }\n    }\n}\n\n// MARK: - Codex RPC client (local process)\n\nprivate struct RPCAccountResponse: Decodable {\n    let account: RPCAccountDetails?\n    let requiresOpenaiAuth: Bool?\n}\n\nprivate enum RPCAccountDetails: Decodable {\n    case apiKey\n    case chatgpt(email: String, planType: String)\n\n    enum CodingKeys: String, CodingKey {\n        case type\n        case email\n        case planType\n    }\n\n    init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        let type = try container.decode(String.self, forKey: .type)\n        switch type.lowercased() {\n        case \"apikey\":\n            self = .apiKey\n        case \"chatgpt\":\n            let email = try container.decodeIfPresent(String.self, forKey: .email) ?? \"unknown\"\n            let plan = try container.decodeIfPresent(String.self, forKey: .planType) ?? \"unknown\"\n            self = .chatgpt(email: email, planType: plan)\n        default:\n            throw DecodingError.dataCorruptedError(\n                forKey: .type,\n                in: container,\n                debugDescription: \"Unknown account type \\(type)\")\n        }\n    }\n}\n\nprivate struct RPCRateLimitsResponse: Decodable, Encodable {\n    let rateLimits: RPCRateLimitSnapshot\n}\n\nprivate struct RPCRateLimitSnapshot: Decodable, Encodable {\n    let primary: RPCRateLimitWindow?\n    let secondary: RPCRateLimitWindow?\n    let credits: RPCCreditsSnapshot?\n}\n\nprivate struct RPCRateLimitWindow: Decodable, Encodable {\n    let usedPercent: Double\n    let windowDurationMins: Int?\n    let resetsAt: Int?\n}\n\nprivate struct RPCCreditsSnapshot: Decodable, Encodable {\n    let hasCredits: Bool\n    let unlimited: Bool\n    let balance: String?\n}\n\nprivate enum RPCWireError: Error, LocalizedError {\n    case startFailed(String)\n    case requestFailed(String)\n    case malformed(String)\n\n    var errorDescription: String? {\n        switch self {\n        case let .startFailed(message):\n            \"Codex not running. Try running a Codex command first. (\\(message))\"\n        case let .requestFailed(message):\n            \"Codex connection failed: \\(message)\"\n        case let .malformed(message):\n            \"Codex returned invalid data: \\(message)\"\n        }\n    }\n}\n\n/// RPC helper used on background tasks; safe because we confine it to the owning task.\nprivate final class CodexRPCClient: @unchecked Sendable {\n    private static let log = CodexBarLog.logger(LogCategories.codexRPC)\n    private let process = Process()\n    private let stdinPipe = Pipe()\n    private let stdoutPipe = Pipe()\n    private let stderrPipe = Pipe()\n    private let stdoutLineStream: AsyncStream<Data>\n    private let stdoutLineContinuation: AsyncStream<Data>.Continuation\n    private var nextID = 1\n\n    private final class LineBuffer: @unchecked Sendable {\n        private let lock = NSLock()\n        private var buffer = Data()\n\n        func appendAndDrainLines(_ data: Data) -> [Data] {\n            self.lock.lock()\n            defer { self.lock.unlock() }\n\n            self.buffer.append(data)\n            var out: [Data] = []\n            while let newline = self.buffer.firstIndex(of: 0x0A) {\n                let lineData = Data(self.buffer[..<newline])\n                self.buffer.removeSubrange(...newline)\n                if !lineData.isEmpty {\n                    out.append(lineData)\n                }\n            }\n            return out\n        }\n    }\n\n    private static func debugWriteStderr(_ message: String) {\n        #if !os(Linux)\n        fputs(message, stderr)\n        #endif\n    }\n\n    init(\n        executable: String = \"codex\",\n        arguments: [String] = [\"-s\", \"read-only\", \"-a\", \"untrusted\", \"app-server\"]) throws\n    {\n        var stdoutContinuation: AsyncStream<Data>.Continuation!\n        self.stdoutLineStream = AsyncStream<Data> { continuation in\n            stdoutContinuation = continuation\n        }\n        self.stdoutLineContinuation = stdoutContinuation\n\n        let resolvedExec = BinaryLocator.resolveCodexBinary()\n            ?? TTYCommandRunner.which(executable)\n\n        guard let resolvedExec else {\n            Self.log.warning(\"Codex RPC binary not found\", metadata: [\"binary\": executable])\n            throw RPCWireError.startFailed(\n                \"Codex CLI not found. Install with `npm i -g @openai/codex` (or bun) then relaunch CodexBar.\")\n        }\n        var env = ProcessInfo.processInfo.environment\n        env[\"PATH\"] = PathBuilder.effectivePATH(\n            purposes: [.rpc, .nodeTooling],\n            env: env)\n\n        self.process.environment = env\n        self.process.executableURL = URL(fileURLWithPath: \"/usr/bin/env\")\n        self.process.arguments = [resolvedExec] + arguments\n        self.process.standardInput = self.stdinPipe\n        self.process.standardOutput = self.stdoutPipe\n        self.process.standardError = self.stderrPipe\n\n        do {\n            try self.process.run()\n            Self.log.debug(\"Codex RPC started\", metadata: [\"binary\": resolvedExec])\n        } catch {\n            Self.log.warning(\"Codex RPC failed to start\", metadata: [\"error\": error.localizedDescription])\n            throw RPCWireError.startFailed(error.localizedDescription)\n        }\n\n        let stdoutHandle = self.stdoutPipe.fileHandleForReading\n        let stdoutLineContinuation = self.stdoutLineContinuation\n        let stdoutBuffer = LineBuffer()\n        stdoutHandle.readabilityHandler = { handle in\n            let data = handle.availableData\n            if data.isEmpty {\n                handle.readabilityHandler = nil\n                stdoutLineContinuation.finish()\n                return\n            }\n\n            let lines = stdoutBuffer.appendAndDrainLines(data)\n\n            for lineData in lines {\n                stdoutLineContinuation.yield(lineData)\n            }\n        }\n\n        let stderrHandle = self.stderrPipe.fileHandleForReading\n        stderrHandle.readabilityHandler = { handle in\n            let data = handle.availableData\n            // When the child closes stderr, availableData returns empty and will keep re-firing; clear the handler\n            // to avoid a busy read loop on the file-descriptor monitoring queue.\n            if data.isEmpty {\n                handle.readabilityHandler = nil\n                return\n            }\n            guard let text = String(data: data, encoding: .utf8), !text.isEmpty else { return }\n            for line in text.split(whereSeparator: \\.isNewline) {\n                Self.debugWriteStderr(\"[codex stderr] \\(line)\\n\")\n            }\n        }\n    }\n\n    func initialize(clientName: String, clientVersion: String) async throws {\n        _ = try await self.request(\n            method: \"initialize\",\n            params: [\"clientInfo\": [\"name\": clientName, \"version\": clientVersion]])\n        try self.sendNotification(method: \"initialized\")\n    }\n\n    func fetchAccount() async throws -> RPCAccountResponse {\n        let message = try await self.request(method: \"account/read\")\n        return try self.decodeResult(from: message)\n    }\n\n    func fetchRateLimits() async throws -> RPCRateLimitsResponse {\n        let message = try await self.request(method: \"account/rateLimits/read\")\n        return try self.decodeResult(from: message)\n    }\n\n    func shutdown() {\n        if self.process.isRunning {\n            Self.log.debug(\"Codex RPC stopping\")\n            self.process.terminate()\n        }\n    }\n\n    // MARK: - JSON-RPC helpers\n\n    private func request(method: String, params: [String: Any]? = nil) async throws -> [String: Any] {\n        let id = self.nextID\n        self.nextID += 1\n        try self.sendRequest(id: id, method: method, params: params)\n\n        while true {\n            let message = try await self.readNextMessage()\n\n            if message[\"id\"] == nil, let methodName = message[\"method\"] as? String {\n                Self.debugWriteStderr(\"[codex notify] \\(methodName)\\n\")\n                continue\n            }\n\n            guard let messageID = self.jsonID(message[\"id\"]), messageID == id else { continue }\n\n            if let error = message[\"error\"] as? [String: Any], let messageText = error[\"message\"] as? String {\n                throw RPCWireError.requestFailed(messageText)\n            }\n\n            return message\n        }\n    }\n\n    private func sendNotification(method: String, params: [String: Any]? = nil) throws {\n        let paramsValue: Any = params ?? [:]\n        try self.sendPayload([\"method\": method, \"params\": paramsValue])\n    }\n\n    private func sendRequest(id: Int, method: String, params: [String: Any]?) throws {\n        let paramsValue: Any = params ?? [:]\n        let payload: [String: Any] = [\"id\": id, \"method\": method, \"params\": paramsValue]\n        try self.sendPayload(payload)\n    }\n\n    private func sendPayload(_ payload: [String: Any]) throws {\n        let data = try JSONSerialization.data(withJSONObject: payload)\n        self.stdinPipe.fileHandleForWriting.write(data)\n        self.stdinPipe.fileHandleForWriting.write(Data([0x0A]))\n    }\n\n    private func readNextMessage() async throws -> [String: Any] {\n        for await lineData in self.stdoutLineStream {\n            if lineData.isEmpty { continue }\n            if let json = try? JSONSerialization.jsonObject(with: lineData) as? [String: Any] {\n                return json\n            }\n        }\n        throw RPCWireError.malformed(\"codex app-server closed stdout\")\n    }\n\n    private func decodeResult<T: Decodable>(from message: [String: Any]) throws -> T {\n        guard let result = message[\"result\"] else {\n            throw RPCWireError.malformed(\"missing result field\")\n        }\n        let data = try JSONSerialization.data(withJSONObject: result)\n        let decoder = JSONDecoder()\n        return try decoder.decode(T.self, from: data)\n    }\n\n    private func jsonID(_ value: Any?) -> Int? {\n        switch value {\n        case let int as Int:\n            int\n        case let number as NSNumber:\n            number.intValue\n        default:\n            nil\n        }\n    }\n}\n\n// MARK: - Public fetcher used by the app\n\npublic struct UsageFetcher: Sendable {\n    private let environment: [String: String]\n\n    public init(environment: [String: String] = ProcessInfo.processInfo.environment) {\n        self.environment = environment\n        LoginShellPathCache.shared.captureOnce()\n    }\n\n    public func loadLatestUsage(keepCLISessionsAlive: Bool = false) async throws -> UsageSnapshot {\n        try await self.withFallback(\n            primary: self.loadRPCUsage,\n            secondary: { try await self.loadTTYUsage(keepCLISessionsAlive: keepCLISessionsAlive) })\n    }\n\n    private func loadRPCUsage() async throws -> UsageSnapshot {\n        let rpc = try CodexRPCClient()\n        defer { rpc.shutdown() }\n\n        try await rpc.initialize(clientName: \"codexbar\", clientVersion: \"0.5.4\")\n        // The app-server answers on a single stdout stream, so keep requests\n        // serialized to avoid starving one reader when multiple awaiters race\n        // for the same pipe.\n        let limits = try await rpc.fetchRateLimits().rateLimits\n        let account = try? await rpc.fetchAccount()\n\n        guard let primary = Self.makeWindow(from: limits.primary),\n              let secondary = Self.makeWindow(from: limits.secondary)\n        else {\n            throw UsageError.noRateLimitsFound\n        }\n\n        let identity = ProviderIdentitySnapshot(\n            providerID: .codex,\n            accountEmail: account?.account.flatMap { details in\n                if case let .chatgpt(email, _) = details { email } else { nil }\n            },\n            accountOrganization: nil,\n            loginMethod: account?.account.flatMap { details in\n                if case let .chatgpt(_, plan) = details { plan } else { nil }\n            })\n        return UsageSnapshot(\n            primary: primary,\n            secondary: secondary,\n            tertiary: nil,\n            updatedAt: Date(),\n            identity: identity)\n    }\n\n    private func loadTTYUsage(keepCLISessionsAlive: Bool) async throws -> UsageSnapshot {\n        let status = try await CodexStatusProbe(keepCLISessionsAlive: keepCLISessionsAlive).fetch()\n        guard let fiveLeft = status.fiveHourPercentLeft, let weekLeft = status.weeklyPercentLeft else {\n            throw UsageError.noRateLimitsFound\n        }\n\n        let primary = RateWindow(\n            usedPercent: max(0, 100 - Double(fiveLeft)),\n            windowMinutes: 300,\n            resetsAt: nil,\n            resetDescription: status.fiveHourResetDescription)\n        let secondary = RateWindow(\n            usedPercent: max(0, 100 - Double(weekLeft)),\n            windowMinutes: 10080,\n            resetsAt: nil,\n            resetDescription: status.weeklyResetDescription)\n\n        return UsageSnapshot(\n            primary: primary,\n            secondary: secondary,\n            tertiary: nil,\n            updatedAt: Date(),\n            identity: nil)\n    }\n\n    public func loadLatestCredits(keepCLISessionsAlive: Bool = false) async throws -> CreditsSnapshot {\n        try await self.withFallback(\n            primary: self.loadRPCCredits,\n            secondary: { try await self.loadTTYCredits(keepCLISessionsAlive: keepCLISessionsAlive) })\n    }\n\n    private func loadRPCCredits() async throws -> CreditsSnapshot {\n        let rpc = try CodexRPCClient()\n        defer { rpc.shutdown() }\n        try await rpc.initialize(clientName: \"codexbar\", clientVersion: \"0.5.4\")\n        let limits = try await rpc.fetchRateLimits().rateLimits\n        guard let credits = limits.credits else { throw UsageError.noRateLimitsFound }\n        let remaining = Self.parseCredits(credits.balance)\n        return CreditsSnapshot(remaining: remaining, events: [], updatedAt: Date())\n    }\n\n    private func loadTTYCredits(keepCLISessionsAlive: Bool) async throws -> CreditsSnapshot {\n        let status = try await CodexStatusProbe(keepCLISessionsAlive: keepCLISessionsAlive).fetch()\n        guard let credits = status.credits else { throw UsageError.noRateLimitsFound }\n        return CreditsSnapshot(remaining: credits, events: [], updatedAt: Date())\n    }\n\n    private func withFallback<T>(\n        primary: @escaping () async throws -> T,\n        secondary: @escaping () async throws -> T) async throws -> T\n    {\n        do {\n            return try await primary()\n        } catch let primaryError {\n            do {\n                return try await secondary()\n            } catch {\n                // Preserve the original failure so callers see the primary path error.\n                throw primaryError\n            }\n        }\n    }\n\n    public func debugRawRateLimits() async -> String {\n        do {\n            let rpc = try CodexRPCClient()\n            defer { rpc.shutdown() }\n            try await rpc.initialize(clientName: \"codexbar\", clientVersion: \"0.5.4\")\n            let limits = try await rpc.fetchRateLimits()\n            let data = try JSONEncoder().encode(limits)\n            return String(data: data, encoding: .utf8) ?? \"<unprintable>\"\n        } catch {\n            return \"Codex RPC probe failed: \\(error)\"\n        }\n    }\n\n    public func loadAccountInfo() -> AccountInfo {\n        // Keep using auth.json for quick startup (non-blocking, no RPC spin-up required).\n        let authURL = URL(fileURLWithPath: self.environment[\"CODEX_HOME\"] ?? \"\\(NSHomeDirectory())/.codex\")\n            .appendingPathComponent(\"auth.json\")\n        guard let data = try? Data(contentsOf: authURL),\n              let auth = try? JSONDecoder().decode(AuthFile.self, from: data),\n              let idToken = auth.tokens?.idToken\n        else {\n            return AccountInfo(email: nil, plan: nil)\n        }\n\n        guard let payload = UsageFetcher.parseJWT(idToken) else {\n            return AccountInfo(email: nil, plan: nil)\n        }\n\n        let authDict = payload[\"https://api.openai.com/auth\"] as? [String: Any]\n        let profileDict = payload[\"https://api.openai.com/profile\"] as? [String: Any]\n\n        let plan = (authDict?[\"chatgpt_plan_type\"] as? String)\n            ?? (payload[\"chatgpt_plan_type\"] as? String)\n\n        let email = (payload[\"email\"] as? String)\n            ?? (profileDict?[\"email\"] as? String)\n\n        return AccountInfo(email: email, plan: plan)\n    }\n\n    // MARK: - Helpers\n\n    private static func makeWindow(from rpc: RPCRateLimitWindow?) -> RateWindow? {\n        guard let rpc else { return nil }\n        let resetsAtDate = rpc.resetsAt.map { Date(timeIntervalSince1970: TimeInterval($0)) }\n        let resetDescription = resetsAtDate.map { UsageFormatter.resetDescription(from: $0) }\n        return RateWindow(\n            usedPercent: rpc.usedPercent,\n            windowMinutes: rpc.windowDurationMins,\n            resetsAt: resetsAtDate,\n            resetDescription: resetDescription)\n    }\n\n    private static func parseCredits(_ balance: String?) -> Double {\n        guard let balance, let val = Double(balance) else { return 0 }\n        return val\n    }\n\n    public static func parseJWT(_ token: String) -> [String: Any]? {\n        let parts = token.split(separator: \".\")\n        guard parts.count >= 2 else { return nil }\n        let payloadPart = parts[1]\n\n        var padded = String(payloadPart)\n            .replacingOccurrences(of: \"-\", with: \"+\")\n            .replacingOccurrences(of: \"_\", with: \"/\")\n        while padded.count % 4 != 0 {\n            padded.append(\"=\")\n        }\n        guard let data = Data(base64Encoded: padded) else { return nil }\n        guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { return nil }\n        return json\n    }\n}\n\n/// Minimal auth.json struct preserved from previous implementation\nprivate struct AuthFile: Decodable {\n    struct Tokens: Decodable { let idToken: String? }\n    let tokens: Tokens?\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/UsageFormatter.swift",
    "content": "import Foundation\n\npublic enum ResetTimeDisplayStyle: String, Codable, Sendable {\n    case countdown\n    case absolute\n}\n\npublic enum UsageFormatter {\n    public static func usageLine(remaining: Double, used: Double, showUsed: Bool) -> String {\n        let percent = showUsed ? used : remaining\n        let clamped = min(100, max(0, percent))\n        let suffix = showUsed ? \"used\" : \"left\"\n        return String(format: \"%.0f%% %@\", clamped, suffix)\n    }\n\n    public static func resetCountdownDescription(from date: Date, now: Date = .init()) -> String {\n        let seconds = max(0, date.timeIntervalSince(now))\n        if seconds < 1 { return \"now\" }\n\n        let totalMinutes = max(1, Int(ceil(seconds / 60.0)))\n        let days = totalMinutes / (24 * 60)\n        let hours = (totalMinutes / 60) % 24\n        let minutes = totalMinutes % 60\n\n        if days > 0 {\n            if hours > 0 { return \"in \\(days)d \\(hours)h\" }\n            return \"in \\(days)d\"\n        }\n        if hours > 0 {\n            if minutes > 0 { return \"in \\(hours)h \\(minutes)m\" }\n            return \"in \\(hours)h\"\n        }\n        return \"in \\(totalMinutes)m\"\n    }\n\n    public static func resetDescription(from date: Date, now: Date = .init()) -> String {\n        // Human-friendly phrasing: today / tomorrow / date+time.\n        let calendar = Calendar.current\n        if calendar.isDate(date, inSameDayAs: now) {\n            return date.formatted(date: .omitted, time: .shortened)\n        }\n        if let tomorrow = calendar.date(byAdding: .day, value: 1, to: now),\n           calendar.isDate(date, inSameDayAs: tomorrow)\n        {\n            return \"tomorrow, \\(date.formatted(date: .omitted, time: .shortened))\"\n        }\n        return date.formatted(date: .abbreviated, time: .shortened)\n    }\n\n    public static func resetLine(\n        for window: RateWindow,\n        style: ResetTimeDisplayStyle,\n        now: Date = .init()) -> String?\n    {\n        if let date = window.resetsAt {\n            let text = style == .countdown\n                ? self.resetCountdownDescription(from: date, now: now)\n                : self.resetDescription(from: date, now: now)\n            return \"Resets \\(text)\"\n        }\n\n        if let desc = window.resetDescription {\n            let trimmed = desc.trimmingCharacters(in: .whitespacesAndNewlines)\n            guard !trimmed.isEmpty else { return nil }\n            if trimmed.lowercased().hasPrefix(\"resets\") { return trimmed }\n            return \"Resets \\(trimmed)\"\n        }\n        return nil\n    }\n\n    public static func updatedString(from date: Date, now: Date = .init()) -> String {\n        let delta = now.timeIntervalSince(date)\n        if abs(delta) < 60 {\n            return \"Updated just now\"\n        }\n        if let hours = Calendar.current.dateComponents([.hour], from: date, to: now).hour, hours < 24 {\n            #if os(macOS)\n            let rel = RelativeDateTimeFormatter()\n            rel.unitsStyle = .abbreviated\n            return \"Updated \\(rel.localizedString(for: date, relativeTo: now))\"\n            #else\n            let seconds = max(0, Int(now.timeIntervalSince(date)))\n            if seconds < 3600 {\n                let minutes = max(1, seconds / 60)\n                return \"Updated \\(minutes)m ago\"\n            }\n            let wholeHours = max(1, seconds / 3600)\n            return \"Updated \\(wholeHours)h ago\"\n            #endif\n        } else {\n            return \"Updated \\(date.formatted(date: .omitted, time: .shortened))\"\n        }\n    }\n\n    public static func creditsString(from value: Double) -> String {\n        let number = NumberFormatter()\n        number.numberStyle = .decimal\n        number.maximumFractionDigits = 2\n        // Use explicit locale for consistent formatting on all systems\n        number.locale = Locale(identifier: \"en_US_POSIX\")\n        let formatted = number.string(from: NSNumber(value: value)) ?? String(format: \"%.2f\", value)\n        return \"\\(formatted) left\"\n    }\n\n    /// Formats a USD value with proper negative handling and thousand separators.\n    /// Uses Swift's modern FormatStyle API (iOS 15+/macOS 12+) for robust, locale-aware formatting.\n    public static func usdString(_ value: Double) -> String {\n        value.formatted(.currency(code: \"USD\").locale(Locale(identifier: \"en_US\")))\n    }\n\n    /// Formats a currency value with the specified currency code.\n    /// Uses FormatStyle with explicit en_US locale to ensure consistent formatting\n    /// regardless of the user's system locale (e.g., pt-BR users see $54.72 not US$ 54,72).\n    public static func currencyString(_ value: Double, currencyCode: String) -> String {\n        value.formatted(.currency(code: currencyCode).locale(Locale(identifier: \"en_US\")))\n    }\n\n    public static func tokenCountString(_ value: Int) -> String {\n        let absValue = abs(value)\n        let sign = value < 0 ? \"-\" : \"\"\n\n        let units: [(threshold: Int, divisor: Double, suffix: String)] = [\n            (1_000_000_000, 1_000_000_000, \"B\"),\n            (1_000_000, 1_000_000, \"M\"),\n            (1000, 1000, \"K\"),\n        ]\n\n        for unit in units where absValue >= unit.threshold {\n            let scaled = Double(absValue) / unit.divisor\n            let formatted: String\n            if scaled >= 10 {\n                formatted = String(format: \"%.0f\", scaled)\n            } else {\n                var s = String(format: \"%.1f\", scaled)\n                if s.hasSuffix(\".0\") { s.removeLast(2) }\n                formatted = s\n            }\n            return \"\\(sign)\\(formatted)\\(unit.suffix)\"\n        }\n\n        let formatter = NumberFormatter()\n        formatter.numberStyle = .decimal\n        formatter.usesGroupingSeparator = true\n        formatter.locale = Locale(identifier: \"en_US_POSIX\")\n        return formatter.string(from: NSNumber(value: value)) ?? \"\\(value)\"\n    }\n\n    public static func creditEventSummary(_ event: CreditEvent) -> String {\n        let formatter = DateFormatter()\n        formatter.dateStyle = .medium\n        let number = NumberFormatter()\n        number.numberStyle = .decimal\n        number.maximumFractionDigits = 2\n        let credits = number.string(from: NSNumber(value: event.creditsUsed)) ?? \"0\"\n        return \"\\(formatter.string(from: event.date)) · \\(event.service) · \\(credits) credits\"\n    }\n\n    public static func creditEventCompact(_ event: CreditEvent) -> String {\n        let formatter = DateFormatter()\n        formatter.dateFormat = \"MMM d\"\n        let number = NumberFormatter()\n        number.numberStyle = .decimal\n        number.maximumFractionDigits = 2\n        let credits = number.string(from: NSNumber(value: event.creditsUsed)) ?? \"0\"\n        return \"\\(formatter.string(from: event.date)) — \\(event.service): \\(credits)\"\n    }\n\n    public static func creditShort(_ value: Double) -> String {\n        if value >= 1000 {\n            let k = value / 1000\n            return String(format: \"%.1fk\", k)\n        }\n        return String(format: \"%.0f\", value)\n    }\n\n    public static func truncatedSingleLine(_ text: String, max: Int = 80) -> String {\n        let single = text\n            .replacingOccurrences(of: \"\\n\", with: \" \")\n            .trimmingCharacters(in: .whitespacesAndNewlines)\n        guard single.count > max else { return single }\n        let idx = single.index(single.startIndex, offsetBy: max)\n        return \"\\(single[..<idx])…\"\n    }\n\n    public static func modelDisplayName(_ raw: String) -> String {\n        var cleaned = raw.trimmingCharacters(in: .whitespacesAndNewlines)\n        guard !cleaned.isEmpty else { return raw }\n\n        let patterns = [\n            #\"(?:-|\\s)\\d{8}$\"#,\n            #\"(?:-|\\s)\\d{4}-\\d{2}-\\d{2}$\"#,\n            #\"\\s\\d{4}\\s\\d{4}$\"#,\n        ]\n\n        for pattern in patterns {\n            if let range = cleaned.range(of: pattern, options: .regularExpression) {\n                cleaned.removeSubrange(range)\n                break\n            }\n        }\n\n        if let trailing = cleaned.range(of: #\"[ \\t-]+$\"#, options: .regularExpression) {\n            cleaned.removeSubrange(trailing)\n        }\n\n        return cleaned.isEmpty ? raw : cleaned\n    }\n\n    public static func modelCostDetail(_ model: String, costUSD: Double?, totalTokens: Int? = nil) -> String? {\n        let costDetail: String? = if let label = CostUsagePricing.codexDisplayLabel(model: model) {\n            label\n        } else if let costUSD {\n            self.usdString(costUSD)\n        } else {\n            nil\n        }\n\n        let tokenDetail = totalTokens.map(self.tokenCountString)\n        let parts = [costDetail, tokenDetail].compactMap(\\.self)\n        guard !parts.isEmpty else { return nil }\n        return parts.joined(separator: \" · \")\n    }\n\n    /// Cleans a provider plan string: strip ANSI/bracket noise, drop boilerplate words, collapse whitespace, and\n    /// ensure a leading capital if the result starts lowercase.\n    public static func cleanPlanName(_ text: String) -> String {\n        let stripped = TextParsing.stripANSICodes(text)\n        let withoutCodes = stripped.replacingOccurrences(\n            of: #\"^\\s*(?:\\[\\d{1,3}m\\s*)+\"#,\n            with: \"\",\n            options: [.regularExpression])\n        let withoutBoilerplate = withoutCodes.replacingOccurrences(\n            of: #\"(?i)\\b(claude|codex|account|plan)\\b\"#,\n            with: \"\",\n            options: [.regularExpression])\n        var cleaned = withoutBoilerplate\n            .replacingOccurrences(of: #\"\\s+\"#, with: \" \", options: .regularExpression)\n            .trimmingCharacters(in: .whitespacesAndNewlines)\n        if cleaned.isEmpty {\n            cleaned = stripped.trimmingCharacters(in: .whitespacesAndNewlines)\n        }\n        if cleaned.lowercased() == \"oauth\" {\n            return \"Ollama\"\n        }\n        // Capitalize first letter only if lowercase, preserving acronyms like \"AI\"\n        if let first = cleaned.first, first.isLowercase {\n            return cleaned.prefix(1).uppercased() + cleaned.dropFirst()\n        }\n        return cleaned\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/UsagePace.swift",
    "content": "import Foundation\n\npublic struct UsagePace: Sendable {\n    public enum Stage: Sendable {\n        case onTrack\n        case slightlyAhead\n        case ahead\n        case farAhead\n        case slightlyBehind\n        case behind\n        case farBehind\n    }\n\n    public let stage: Stage\n    public let deltaPercent: Double\n    public let expectedUsedPercent: Double\n    public let actualUsedPercent: Double\n    public let etaSeconds: TimeInterval?\n    public let willLastToReset: Bool\n    public let runOutProbability: Double?\n\n    public init(\n        stage: Stage,\n        deltaPercent: Double,\n        expectedUsedPercent: Double,\n        actualUsedPercent: Double,\n        etaSeconds: TimeInterval?,\n        willLastToReset: Bool,\n        runOutProbability: Double? = nil)\n    {\n        self.stage = stage\n        self.deltaPercent = deltaPercent\n        self.expectedUsedPercent = expectedUsedPercent\n        self.actualUsedPercent = actualUsedPercent\n        self.etaSeconds = etaSeconds\n        self.willLastToReset = willLastToReset\n        self.runOutProbability = runOutProbability\n    }\n\n    public static func weekly(\n        window: RateWindow,\n        now: Date = .init(),\n        defaultWindowMinutes: Int = 10080) -> UsagePace?\n    {\n        guard let resetsAt = window.resetsAt else { return nil }\n        let minutes = window.windowMinutes ?? defaultWindowMinutes\n        guard minutes > 0 else { return nil }\n\n        let duration = TimeInterval(minutes) * 60\n        let timeUntilReset = resetsAt.timeIntervalSince(now)\n        guard timeUntilReset > 0 else { return nil }\n        guard timeUntilReset <= duration else { return nil }\n        let elapsed = (duration - timeUntilReset).clamped(to: 0...duration)\n        let expected = ((elapsed / duration) * 100).clamped(to: 0...100)\n        let actual = window.usedPercent.clamped(to: 0...100)\n        if elapsed == 0, actual > 0 {\n            return nil\n        }\n        let delta = actual - expected\n        let stage = Self.stage(for: delta)\n\n        var etaSeconds: TimeInterval?\n        var willLastToReset = false\n\n        if elapsed > 0, actual > 0 {\n            let rate = actual / elapsed\n            if rate > 0 {\n                let remaining = max(0, 100 - actual)\n                let candidate = remaining / rate\n                if candidate >= timeUntilReset {\n                    willLastToReset = true\n                } else {\n                    etaSeconds = candidate\n                }\n            }\n        } else if elapsed > 0, actual == 0 {\n            willLastToReset = true\n        }\n\n        return UsagePace(\n            stage: stage,\n            deltaPercent: delta,\n            expectedUsedPercent: expected,\n            actualUsedPercent: actual,\n            etaSeconds: etaSeconds,\n            willLastToReset: willLastToReset,\n            runOutProbability: nil)\n    }\n\n    public static func historical(\n        expectedUsedPercent: Double,\n        actualUsedPercent: Double,\n        etaSeconds: TimeInterval?,\n        willLastToReset: Bool,\n        runOutProbability: Double?) -> UsagePace\n    {\n        let expected = expectedUsedPercent.clamped(to: 0...100)\n        let actual = actualUsedPercent.clamped(to: 0...100)\n        let delta = actual - expected\n        return UsagePace(\n            stage: Self.stage(for: delta),\n            deltaPercent: delta,\n            expectedUsedPercent: expected,\n            actualUsedPercent: actual,\n            etaSeconds: etaSeconds,\n            willLastToReset: willLastToReset,\n            runOutProbability: runOutProbability)\n    }\n\n    private static func stage(for delta: Double) -> Stage {\n        let absDelta = abs(delta)\n        if absDelta <= 2 { return .onTrack }\n        if absDelta <= 6 { return delta >= 0 ? .slightlyAhead : .slightlyBehind }\n        if absDelta <= 12 { return delta >= 0 ? .ahead : .behind }\n        return delta >= 0 ? .farAhead : .farBehind\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Vendored/CostUsage/CostUsageCache.swift",
    "content": "import Foundation\n\nenum CostUsageCacheIO {\n    private static func artifactVersion(for provider: UsageProvider) -> Int {\n        switch provider {\n        case .codex:\n            2\n        default:\n            1\n        }\n    }\n\n    private static func defaultCacheRoot() -> URL {\n        let root = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!\n        return root.appendingPathComponent(\"CodexBar\", isDirectory: true)\n    }\n\n    static func cacheFileURL(provider: UsageProvider, cacheRoot: URL? = nil) -> URL {\n        let root = cacheRoot ?? self.defaultCacheRoot()\n        let artifactVersion = self.artifactVersion(for: provider)\n        return root\n            .appendingPathComponent(\"cost-usage\", isDirectory: true)\n            .appendingPathComponent(\"\\(provider.rawValue)-v\\(artifactVersion).json\", isDirectory: false)\n    }\n\n    static func load(provider: UsageProvider, cacheRoot: URL? = nil) -> CostUsageCache {\n        let url = self.cacheFileURL(provider: provider, cacheRoot: cacheRoot)\n        if let decoded = self.loadCache(at: url) { return decoded }\n        return CostUsageCache()\n    }\n\n    private static func loadCache(at url: URL) -> CostUsageCache? {\n        guard let data = try? Data(contentsOf: url) else { return nil }\n        guard let decoded = try? JSONDecoder().decode(CostUsageCache.self, from: data)\n        else { return nil }\n        guard decoded.version == 1 else { return nil }\n        return decoded\n    }\n\n    static func save(provider: UsageProvider, cache: CostUsageCache, cacheRoot: URL? = nil) {\n        let url = self.cacheFileURL(provider: provider, cacheRoot: cacheRoot)\n        let dir = url.deletingLastPathComponent()\n        try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)\n\n        let tmp = dir.appendingPathComponent(\".tmp-\\(UUID().uuidString).json\", isDirectory: false)\n        let data = (try? JSONEncoder().encode(cache)) ?? Data()\n        do {\n            try data.write(to: tmp, options: [.atomic])\n            _ = try FileManager.default.replaceItemAt(url, withItemAt: tmp)\n        } catch {\n            try? FileManager.default.removeItem(at: tmp)\n        }\n    }\n}\n\nstruct CostUsageCache: Codable {\n    var version: Int = 1\n    var lastScanUnixMs: Int64 = 0\n\n    /// filePath -> file usage\n    var files: [String: CostUsageFileUsage] = [:]\n\n    /// dayKey -> model -> packed usage\n    var days: [String: [String: [Int]]] = [:]\n\n    /// rootPath -> mtime (for Claude roots)\n    var roots: [String: Int64]?\n}\n\nstruct CostUsageFileUsage: Codable {\n    var mtimeUnixMs: Int64\n    var size: Int64\n    var days: [String: [String: [Int]]]\n    var parsedBytes: Int64?\n    var lastModel: String?\n    var lastTotals: CostUsageCodexTotals?\n    var sessionId: String?\n}\n\nstruct CostUsageCodexTotals: Codable {\n    var input: Int\n    var cached: Int\n    var output: Int\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Vendored/CostUsage/CostUsageJsonl.swift",
    "content": "import Foundation\n\nenum CostUsageJsonl {\n    struct Line {\n        let bytes: Data\n        let wasTruncated: Bool\n    }\n\n    @discardableResult\n    static func scan(\n        fileURL: URL,\n        offset: Int64 = 0,\n        maxLineBytes: Int,\n        prefixBytes: Int,\n        onLine: (Line) -> Void) throws\n        -> Int64\n    {\n        let handle = try FileHandle(forReadingFrom: fileURL)\n        defer { try? handle.close() }\n\n        let startOffset = max(0, offset)\n        if startOffset > 0 {\n            try handle.seek(toOffset: UInt64(startOffset))\n        }\n\n        var current = Data()\n        current.reserveCapacity(4 * 1024)\n        var lineBytes = 0\n        var truncated = false\n        var bytesRead: Int64 = 0\n\n        func appendSegment(_ segment: Data.SubSequence) {\n            guard !segment.isEmpty else { return }\n            lineBytes += segment.count\n            guard !truncated else { return }\n            if lineBytes > maxLineBytes || lineBytes > prefixBytes {\n                truncated = true\n                current.removeAll(keepingCapacity: true)\n                return\n            }\n            current.append(contentsOf: segment)\n        }\n\n        func flushLine() {\n            guard lineBytes > 0 else { return }\n            let line = Line(bytes: current, wasTruncated: truncated)\n            onLine(line)\n            current.removeAll(keepingCapacity: true)\n            lineBytes = 0\n            truncated = false\n        }\n\n        while true {\n            let chunk = try handle.read(upToCount: 256 * 1024) ?? Data()\n            if chunk.isEmpty {\n                flushLine()\n                break\n            }\n\n            bytesRead += Int64(chunk.count)\n            var segmentStart = chunk.startIndex\n            while let nl = chunk[segmentStart...].firstIndex(of: 0x0A) {\n                appendSegment(chunk[segmentStart..<nl])\n                flushLine()\n                segmentStart = chunk.index(after: nl)\n            }\n            if segmentStart < chunk.endIndex {\n                appendSegment(chunk[segmentStart..<chunk.endIndex])\n            }\n        }\n\n        return startOffset + bytesRead\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Vendored/CostUsage/CostUsagePricing.swift",
    "content": "import Foundation\n\nenum CostUsagePricing {\n    struct CodexPricing {\n        let inputCostPerToken: Double\n        let outputCostPerToken: Double\n        let cacheReadInputCostPerToken: Double?\n        let displayLabel: String?\n    }\n\n    struct ClaudePricing {\n        let inputCostPerToken: Double\n        let outputCostPerToken: Double\n        let cacheCreationInputCostPerToken: Double\n        let cacheReadInputCostPerToken: Double\n\n        let thresholdTokens: Int?\n        let inputCostPerTokenAboveThreshold: Double?\n        let outputCostPerTokenAboveThreshold: Double?\n        let cacheCreationInputCostPerTokenAboveThreshold: Double?\n        let cacheReadInputCostPerTokenAboveThreshold: Double?\n    }\n\n    private static let codex: [String: CodexPricing] = [\n        \"gpt-5\": CodexPricing(\n            inputCostPerToken: 1.25e-6,\n            outputCostPerToken: 1e-5,\n            cacheReadInputCostPerToken: 1.25e-7,\n            displayLabel: nil),\n        \"gpt-5-codex\": CodexPricing(\n            inputCostPerToken: 1.25e-6,\n            outputCostPerToken: 1e-5,\n            cacheReadInputCostPerToken: 1.25e-7,\n            displayLabel: nil),\n        \"gpt-5-mini\": CodexPricing(\n            inputCostPerToken: 2.5e-7,\n            outputCostPerToken: 2e-6,\n            cacheReadInputCostPerToken: 2.5e-8,\n            displayLabel: nil),\n        \"gpt-5-nano\": CodexPricing(\n            inputCostPerToken: 5e-8,\n            outputCostPerToken: 4e-7,\n            cacheReadInputCostPerToken: 5e-9,\n            displayLabel: nil),\n        \"gpt-5-pro\": CodexPricing(\n            inputCostPerToken: 1.5e-5,\n            outputCostPerToken: 1.2e-4,\n            cacheReadInputCostPerToken: nil,\n            displayLabel: nil),\n        \"gpt-5.1\": CodexPricing(\n            inputCostPerToken: 1.25e-6,\n            outputCostPerToken: 1e-5,\n            cacheReadInputCostPerToken: 1.25e-7,\n            displayLabel: nil),\n        \"gpt-5.1-codex\": CodexPricing(\n            inputCostPerToken: 1.25e-6,\n            outputCostPerToken: 1e-5,\n            cacheReadInputCostPerToken: 1.25e-7,\n            displayLabel: nil),\n        \"gpt-5.1-codex-max\": CodexPricing(\n            inputCostPerToken: 1.25e-6,\n            outputCostPerToken: 1e-5,\n            cacheReadInputCostPerToken: 1.25e-7,\n            displayLabel: nil),\n        \"gpt-5.1-codex-mini\": CodexPricing(\n            inputCostPerToken: 2.5e-7,\n            outputCostPerToken: 2e-6,\n            cacheReadInputCostPerToken: 2.5e-8,\n            displayLabel: nil),\n        \"gpt-5.2\": CodexPricing(\n            inputCostPerToken: 1.75e-6,\n            outputCostPerToken: 1.4e-5,\n            cacheReadInputCostPerToken: 1.75e-7,\n            displayLabel: nil),\n        \"gpt-5.2-codex\": CodexPricing(\n            inputCostPerToken: 1.75e-6,\n            outputCostPerToken: 1.4e-5,\n            cacheReadInputCostPerToken: 1.75e-7,\n            displayLabel: nil),\n        \"gpt-5.2-pro\": CodexPricing(\n            inputCostPerToken: 2.1e-5,\n            outputCostPerToken: 1.68e-4,\n            cacheReadInputCostPerToken: nil,\n            displayLabel: nil),\n        \"gpt-5.3-codex\": CodexPricing(\n            inputCostPerToken: 1.75e-6,\n            outputCostPerToken: 1.4e-5,\n            cacheReadInputCostPerToken: 1.75e-7,\n            displayLabel: nil),\n        \"gpt-5.3-codex-spark\": CodexPricing(\n            inputCostPerToken: 0,\n            outputCostPerToken: 0,\n            cacheReadInputCostPerToken: 0,\n            displayLabel: \"Research Preview\"),\n        \"gpt-5.4\": CodexPricing(\n            inputCostPerToken: 2.5e-6,\n            outputCostPerToken: 1.5e-5,\n            cacheReadInputCostPerToken: 2.5e-7,\n            displayLabel: nil),\n        \"gpt-5.4-mini\": CodexPricing(\n            inputCostPerToken: 7.5e-7,\n            outputCostPerToken: 4.5e-6,\n            cacheReadInputCostPerToken: 7.5e-8,\n            displayLabel: nil),\n        \"gpt-5.4-nano\": CodexPricing(\n            inputCostPerToken: 2e-7,\n            outputCostPerToken: 1.25e-6,\n            cacheReadInputCostPerToken: 2e-8,\n            displayLabel: nil),\n        \"gpt-5.4-pro\": CodexPricing(\n            inputCostPerToken: 3e-5,\n            outputCostPerToken: 1.8e-4,\n            cacheReadInputCostPerToken: nil,\n            displayLabel: nil),\n    ]\n\n    private static let claude: [String: ClaudePricing] = [\n        \"claude-haiku-4-5-20251001\": ClaudePricing(\n            inputCostPerToken: 1e-6,\n            outputCostPerToken: 5e-6,\n            cacheCreationInputCostPerToken: 1.25e-6,\n            cacheReadInputCostPerToken: 1e-7,\n            thresholdTokens: nil,\n            inputCostPerTokenAboveThreshold: nil,\n            outputCostPerTokenAboveThreshold: nil,\n            cacheCreationInputCostPerTokenAboveThreshold: nil,\n            cacheReadInputCostPerTokenAboveThreshold: nil),\n        \"claude-haiku-4-5\": ClaudePricing(\n            inputCostPerToken: 1e-6,\n            outputCostPerToken: 5e-6,\n            cacheCreationInputCostPerToken: 1.25e-6,\n            cacheReadInputCostPerToken: 1e-7,\n            thresholdTokens: nil,\n            inputCostPerTokenAboveThreshold: nil,\n            outputCostPerTokenAboveThreshold: nil,\n            cacheCreationInputCostPerTokenAboveThreshold: nil,\n            cacheReadInputCostPerTokenAboveThreshold: nil),\n        \"claude-opus-4-5-20251101\": ClaudePricing(\n            inputCostPerToken: 5e-6,\n            outputCostPerToken: 2.5e-5,\n            cacheCreationInputCostPerToken: 6.25e-6,\n            cacheReadInputCostPerToken: 5e-7,\n            thresholdTokens: nil,\n            inputCostPerTokenAboveThreshold: nil,\n            outputCostPerTokenAboveThreshold: nil,\n            cacheCreationInputCostPerTokenAboveThreshold: nil,\n            cacheReadInputCostPerTokenAboveThreshold: nil),\n        \"claude-opus-4-5\": ClaudePricing(\n            inputCostPerToken: 5e-6,\n            outputCostPerToken: 2.5e-5,\n            cacheCreationInputCostPerToken: 6.25e-6,\n            cacheReadInputCostPerToken: 5e-7,\n            thresholdTokens: nil,\n            inputCostPerTokenAboveThreshold: nil,\n            outputCostPerTokenAboveThreshold: nil,\n            cacheCreationInputCostPerTokenAboveThreshold: nil,\n            cacheReadInputCostPerTokenAboveThreshold: nil),\n        \"claude-opus-4-6-20260205\": ClaudePricing(\n            inputCostPerToken: 5e-6,\n            outputCostPerToken: 2.5e-5,\n            cacheCreationInputCostPerToken: 6.25e-6,\n            cacheReadInputCostPerToken: 5e-7,\n            thresholdTokens: nil,\n            inputCostPerTokenAboveThreshold: nil,\n            outputCostPerTokenAboveThreshold: nil,\n            cacheCreationInputCostPerTokenAboveThreshold: nil,\n            cacheReadInputCostPerTokenAboveThreshold: nil),\n        \"claude-opus-4-6\": ClaudePricing(\n            inputCostPerToken: 5e-6,\n            outputCostPerToken: 2.5e-5,\n            cacheCreationInputCostPerToken: 6.25e-6,\n            cacheReadInputCostPerToken: 5e-7,\n            thresholdTokens: nil,\n            inputCostPerTokenAboveThreshold: nil,\n            outputCostPerTokenAboveThreshold: nil,\n            cacheCreationInputCostPerTokenAboveThreshold: nil,\n            cacheReadInputCostPerTokenAboveThreshold: nil),\n        \"claude-sonnet-4-5\": ClaudePricing(\n            inputCostPerToken: 3e-6,\n            outputCostPerToken: 1.5e-5,\n            cacheCreationInputCostPerToken: 3.75e-6,\n            cacheReadInputCostPerToken: 3e-7,\n            thresholdTokens: 200_000,\n            inputCostPerTokenAboveThreshold: 6e-6,\n            outputCostPerTokenAboveThreshold: 2.25e-5,\n            cacheCreationInputCostPerTokenAboveThreshold: 7.5e-6,\n            cacheReadInputCostPerTokenAboveThreshold: 6e-7),\n        \"claude-sonnet-4-5-20250929\": ClaudePricing(\n            inputCostPerToken: 3e-6,\n            outputCostPerToken: 1.5e-5,\n            cacheCreationInputCostPerToken: 3.75e-6,\n            cacheReadInputCostPerToken: 3e-7,\n            thresholdTokens: 200_000,\n            inputCostPerTokenAboveThreshold: 6e-6,\n            outputCostPerTokenAboveThreshold: 2.25e-5,\n            cacheCreationInputCostPerTokenAboveThreshold: 7.5e-6,\n            cacheReadInputCostPerTokenAboveThreshold: 6e-7),\n        \"claude-opus-4-20250514\": ClaudePricing(\n            inputCostPerToken: 1.5e-5,\n            outputCostPerToken: 7.5e-5,\n            cacheCreationInputCostPerToken: 1.875e-5,\n            cacheReadInputCostPerToken: 1.5e-6,\n            thresholdTokens: nil,\n            inputCostPerTokenAboveThreshold: nil,\n            outputCostPerTokenAboveThreshold: nil,\n            cacheCreationInputCostPerTokenAboveThreshold: nil,\n            cacheReadInputCostPerTokenAboveThreshold: nil),\n        \"claude-opus-4-1\": ClaudePricing(\n            inputCostPerToken: 1.5e-5,\n            outputCostPerToken: 7.5e-5,\n            cacheCreationInputCostPerToken: 1.875e-5,\n            cacheReadInputCostPerToken: 1.5e-6,\n            thresholdTokens: nil,\n            inputCostPerTokenAboveThreshold: nil,\n            outputCostPerTokenAboveThreshold: nil,\n            cacheCreationInputCostPerTokenAboveThreshold: nil,\n            cacheReadInputCostPerTokenAboveThreshold: nil),\n        \"claude-sonnet-4-20250514\": ClaudePricing(\n            inputCostPerToken: 3e-6,\n            outputCostPerToken: 1.5e-5,\n            cacheCreationInputCostPerToken: 3.75e-6,\n            cacheReadInputCostPerToken: 3e-7,\n            thresholdTokens: 200_000,\n            inputCostPerTokenAboveThreshold: 6e-6,\n            outputCostPerTokenAboveThreshold: 2.25e-5,\n            cacheCreationInputCostPerTokenAboveThreshold: 7.5e-6,\n            cacheReadInputCostPerTokenAboveThreshold: 6e-7),\n    ]\n\n    static func normalizeCodexModel(_ raw: String) -> String {\n        var trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)\n        if trimmed.hasPrefix(\"openai/\") {\n            trimmed = String(trimmed.dropFirst(\"openai/\".count))\n        }\n\n        if self.codex[trimmed] != nil {\n            return trimmed\n        }\n\n        if let datedSuffix = trimmed.range(of: #\"-\\d{4}-\\d{2}-\\d{2}$\"#, options: .regularExpression) {\n            let base = String(trimmed[..<datedSuffix.lowerBound])\n            if self.codex[base] != nil {\n                return base\n            }\n        }\n        return trimmed\n    }\n\n    static func codexDisplayLabel(model: String) -> String? {\n        let key = self.normalizeCodexModel(model)\n        return self.codex[key]?.displayLabel\n    }\n\n    static func normalizeClaudeModel(_ raw: String) -> String {\n        var trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)\n        if trimmed.hasPrefix(\"anthropic.\") {\n            trimmed = String(trimmed.dropFirst(\"anthropic.\".count))\n        }\n\n        if let lastDot = trimmed.lastIndex(of: \".\"),\n           trimmed.contains(\"claude-\")\n        {\n            let tail = String(trimmed[trimmed.index(after: lastDot)...])\n            if tail.hasPrefix(\"claude-\") {\n                trimmed = tail\n            }\n        }\n\n        if let vRange = trimmed.range(of: #\"-v\\d+:\\d+$\"#, options: .regularExpression) {\n            trimmed.removeSubrange(vRange)\n        }\n\n        if let baseRange = trimmed.range(of: #\"-\\d{8}$\"#, options: .regularExpression) {\n            let base = String(trimmed[..<baseRange.lowerBound])\n            if self.claude[base] != nil {\n                return base\n            }\n        }\n\n        return trimmed\n    }\n\n    static func codexCostUSD(model: String, inputTokens: Int, cachedInputTokens: Int, outputTokens: Int) -> Double? {\n        let key = self.normalizeCodexModel(model)\n        guard let pricing = self.codex[key] else { return nil }\n        let cached = min(max(0, cachedInputTokens), max(0, inputTokens))\n        let nonCached = max(0, inputTokens - cached)\n        let cachedRate = pricing.cacheReadInputCostPerToken ?? pricing.inputCostPerToken\n        return Double(nonCached) * pricing.inputCostPerToken\n            + Double(cached) * cachedRate\n            + Double(max(0, outputTokens)) * pricing.outputCostPerToken\n    }\n\n    static func claudeCostUSD(\n        model: String,\n        inputTokens: Int,\n        cacheReadInputTokens: Int,\n        cacheCreationInputTokens: Int,\n        outputTokens: Int) -> Double?\n    {\n        let key = self.normalizeClaudeModel(model)\n        guard let pricing = self.claude[key] else { return nil }\n\n        func tiered(_ tokens: Int, base: Double, above: Double?, threshold: Int?) -> Double {\n            guard let threshold, let above else { return Double(tokens) * base }\n            let below = min(tokens, threshold)\n            let over = max(tokens - threshold, 0)\n            return Double(below) * base + Double(over) * above\n        }\n\n        return tiered(\n            max(0, inputTokens),\n            base: pricing.inputCostPerToken,\n            above: pricing.inputCostPerTokenAboveThreshold,\n            threshold: pricing.thresholdTokens)\n            + tiered(\n                max(0, cacheReadInputTokens),\n                base: pricing.cacheReadInputCostPerToken,\n                above: pricing.cacheReadInputCostPerTokenAboveThreshold,\n                threshold: pricing.thresholdTokens)\n            + tiered(\n                max(0, cacheCreationInputTokens),\n                base: pricing.cacheCreationInputCostPerToken,\n                above: pricing.cacheCreationInputCostPerTokenAboveThreshold,\n                threshold: pricing.thresholdTokens)\n            + tiered(\n                max(0, outputTokens),\n                base: pricing.outputCostPerToken,\n                above: pricing.outputCostPerTokenAboveThreshold,\n                threshold: pricing.thresholdTokens)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner+Claude.swift",
    "content": "import Foundation\n\nextension CostUsageScanner {\n    // MARK: - Claude\n\n    private static func defaultClaudeProjectsRoots(options: Options) -> [URL] {\n        if let override = options.claudeProjectsRoots { return override }\n\n        var roots: [URL] = []\n\n        if let env = ProcessInfo.processInfo.environment[\"CLAUDE_CONFIG_DIR\"]?\n            .trimmingCharacters(in: .whitespacesAndNewlines),\n            !env.isEmpty\n        {\n            for part in env.split(separator: \",\") {\n                let raw = String(part).trimmingCharacters(in: .whitespacesAndNewlines)\n                guard !raw.isEmpty else { continue }\n                let url = URL(fileURLWithPath: raw)\n                if url.lastPathComponent == \"projects\" {\n                    roots.append(url)\n                } else {\n                    roots.append(url.appendingPathComponent(\"projects\", isDirectory: true))\n                }\n            }\n        } else {\n            let home = FileManager.default.homeDirectoryForCurrentUser\n            roots.append(home.appendingPathComponent(\".config/claude/projects\", isDirectory: true))\n            roots.append(home.appendingPathComponent(\".claude/projects\", isDirectory: true))\n        }\n\n        return roots\n    }\n\n    static func parseClaudeFile(\n        fileURL: URL,\n        range: CostUsageDayRange,\n        providerFilter: ClaudeLogProviderFilter,\n        startOffset: Int64 = 0) -> ClaudeParseResult\n    {\n        var days: [String: [String: [Int]]] = [:]\n        // Track seen message+request IDs to deduplicate streaming chunks within a JSONL file.\n        // Claude emits multiple lines per message with cumulative usage, so we only count once.\n        var seenKeys: Set<String> = []\n\n        struct ClaudeTokens: Sendable {\n            let input: Int\n            let cacheRead: Int\n            let cacheCreate: Int\n            let output: Int\n            let costNanos: Int\n        }\n\n        func add(dayKey: String, model: String, tokens: ClaudeTokens) {\n            guard CostUsageDayRange.isInRange(dayKey: dayKey, since: range.scanSinceKey, until: range.scanUntilKey)\n            else { return }\n            let normModel = CostUsagePricing.normalizeClaudeModel(model)\n            var dayModels = days[dayKey] ?? [:]\n            var packed = dayModels[normModel] ?? [0, 0, 0, 0, 0]\n            packed[0] = (packed[safe: 0] ?? 0) + tokens.input\n            packed[1] = (packed[safe: 1] ?? 0) + tokens.cacheRead\n            packed[2] = (packed[safe: 2] ?? 0) + tokens.cacheCreate\n            packed[3] = (packed[safe: 3] ?? 0) + tokens.output\n            packed[4] = (packed[safe: 4] ?? 0) + tokens.costNanos\n            dayModels[normModel] = packed\n            days[dayKey] = dayModels\n        }\n\n        let maxLineBytes = 512 * 1024\n        // Keep the full line so usage at the tail isn't dropped on large tool outputs.\n        let prefixBytes = maxLineBytes\n        let costScale = 1_000_000_000.0\n\n        let parsedBytes = (try? CostUsageJsonl.scan(\n            fileURL: fileURL,\n            offset: startOffset,\n            maxLineBytes: maxLineBytes,\n            prefixBytes: prefixBytes,\n            onLine: { line in\n                guard !line.bytes.isEmpty else { return }\n                guard !line.wasTruncated else { return }\n                guard line.bytes.containsAscii(#\"\"type\":\"assistant\"\"#) else { return }\n                guard line.bytes.containsAscii(#\"\"usage\"\"#) else { return }\n\n                guard\n                    let obj = (try? JSONSerialization.jsonObject(with: line.bytes)) as? [String: Any],\n                    let type = obj[\"type\"] as? String,\n                    type == \"assistant\"\n                else { return }\n                guard Self.matchesClaudeProviderFilter(obj: obj, filter: providerFilter) else { return }\n\n                guard let tsText = obj[\"timestamp\"] as? String else { return }\n                guard let dayKey = Self.dayKeyFromTimestamp(tsText) ?? Self.dayKeyFromParsedISO(tsText) else { return }\n\n                guard let message = obj[\"message\"] as? [String: Any] else { return }\n                guard let model = message[\"model\"] as? String else { return }\n                guard let usage = message[\"usage\"] as? [String: Any] else { return }\n\n                // Deduplicate by message.id + requestId (streaming chunks have same usage).\n                let messageId = message[\"id\"] as? String\n                let requestId = obj[\"requestId\"] as? String\n                if let messageId, let requestId {\n                    let key = \"\\(messageId):\\(requestId)\"\n                    if seenKeys.contains(key) { return }\n                    seenKeys.insert(key)\n                } else {\n                    // Older logs omit IDs; treat each line as distinct to avoid dropping usage.\n                }\n\n                func toInt(_ v: Any?) -> Int {\n                    if let n = v as? NSNumber { return n.intValue }\n                    return 0\n                }\n\n                let input = max(0, toInt(usage[\"input_tokens\"]))\n                let cacheCreate = max(0, toInt(usage[\"cache_creation_input_tokens\"]))\n                let cacheRead = max(0, toInt(usage[\"cache_read_input_tokens\"]))\n                let output = max(0, toInt(usage[\"output_tokens\"]))\n                if input == 0, cacheCreate == 0, cacheRead == 0, output == 0 { return }\n\n                let cost = CostUsagePricing.claudeCostUSD(\n                    model: model,\n                    inputTokens: input,\n                    cacheReadInputTokens: cacheRead,\n                    cacheCreationInputTokens: cacheCreate,\n                    outputTokens: output)\n                let costNanos = cost.map { Int(($0 * costScale).rounded()) } ?? 0\n                let tokens = ClaudeTokens(\n                    input: input,\n                    cacheRead: cacheRead,\n                    cacheCreate: cacheCreate,\n                    output: output,\n                    costNanos: costNanos)\n                add(dayKey: dayKey, model: model, tokens: tokens)\n            })) ?? startOffset\n\n        return ClaudeParseResult(days: days, parsedBytes: parsedBytes)\n    }\n\n    private static let vertexProviderKeys: Set<String> = [\n        \"provider\",\n        \"platform\",\n        \"backend\",\n        \"api_provider\",\n        \"apiprovider\",\n        \"api_type\",\n        \"apitype\",\n        \"source\",\n        \"vendor\",\n        \"client\",\n    ]\n\n    private static func matchesClaudeProviderFilter(\n        obj: [String: Any],\n        filter: ClaudeLogProviderFilter) -> Bool\n    {\n        switch filter {\n        case .all:\n            true\n        case .vertexAIOnly:\n            self.isVertexAIUsageEntry(obj: obj)\n        case .excludeVertexAI:\n            !self.isVertexAIUsageEntry(obj: obj)\n        }\n    }\n\n    private static func isVertexAIUsageEntry(obj: [String: Any]) -> Bool {\n        // Primary detection: Vertex AI message IDs and request IDs have \"vrtx\" prefix\n        // e.g., \"msg_vrtx_0154LUXjFVzQGUca3yK2RUeo\", \"req_vrtx_011CWjK86SWeFuXqZKUtgB1H\"\n        if let message = obj[\"message\"] as? [String: Any],\n           let messageId = message[\"id\"] as? String,\n           messageId.contains(\"_vrtx_\")\n        {\n            return true\n        }\n        if let requestId = obj[\"requestId\"] as? String,\n           requestId.contains(\"_vrtx_\")\n        {\n            return true\n        }\n\n        // Secondary detection: model name with @ version separator (Vertex AI format)\n        // e.g., \"claude-opus-4-5@20251101\" vs \"claude-opus-4-5-20251101\"\n        if let message = obj[\"message\"] as? [String: Any],\n           let model = message[\"model\"] as? String,\n           Self.modelNameLooksVertex(model)\n        {\n            return true\n        }\n\n        // Fallback: check for explicit Vertex AI metadata fields\n        var candidates: [[String: Any]] = [obj]\n        if let metadata = obj[\"metadata\"] as? [String: Any] { candidates.append(metadata) }\n        if let request = obj[\"request\"] as? [String: Any] { candidates.append(request) }\n        if let context = obj[\"context\"] as? [String: Any] { candidates.append(context) }\n        if let client = obj[\"client\"] as? [String: Any] { candidates.append(client) }\n        if let message = obj[\"message\"] as? [String: Any] {\n            if let metadata = message[\"metadata\"] as? [String: Any] { candidates.append(metadata) }\n            if let request = message[\"request\"] as? [String: Any] { candidates.append(request) }\n        }\n\n        return candidates.contains { Self.containsVertexAIMetadata(in: $0) }\n    }\n\n    /// Detects Vertex AI model names by format.\n    /// Vertex AI uses @ for version separator: claude-opus-4-5@20251101\n    /// Anthropic API uses -: claude-opus-4-5-20251101\n    private static func modelNameLooksVertex(_ model: String) -> Bool {\n        // Vertex AI model format: claude-{variant}@{version}\n        // Examples: claude-opus-4-5@20251101, claude-sonnet-4-5@20250514\n        guard model.hasPrefix(\"claude-\") else { return false }\n        return model.contains(\"@\")\n    }\n\n    private static func containsVertexAIMetadata(in dict: [String: Any]) -> Bool {\n        for (key, value) in dict {\n            let lowerKey = key.lowercased()\n            if lowerKey.contains(\"vertex\") || lowerKey.contains(\"gcp\") {\n                return true\n            }\n            if Self.vertexProviderKeys.contains(lowerKey),\n               let text = value as? String,\n               Self.stringLooksVertex(text)\n            {\n                return true\n            }\n            if let nested = value as? [String: Any] {\n                if Self.containsVertexAIMetadata(in: nested) { return true }\n            } else if let array = value as? [Any] {\n                if Self.containsVertexAIMetadata(in: array) { return true }\n            }\n        }\n\n        return false\n    }\n\n    private static func containsVertexAIMetadata(in array: [Any]) -> Bool {\n        for entry in array {\n            if let dict = entry as? [String: Any] {\n                if self.containsVertexAIMetadata(in: dict) { return true }\n            }\n        }\n\n        return false\n    }\n\n    private static func stringLooksVertex(_ value: String) -> Bool {\n        value.lowercased().contains(\"vertex\")\n    }\n\n    private static func claudeRootCandidates(for rootPath: String) -> [String] {\n        if rootPath.hasPrefix(\"/var/\") {\n            return [\"/private\" + rootPath, rootPath]\n        }\n        if rootPath.hasPrefix(\"/private/var/\") {\n            let trimmed = String(rootPath.dropFirst(\"/private\".count))\n            return [rootPath, trimmed]\n        }\n        return [rootPath]\n    }\n\n    private final class ClaudeScanState {\n        var cache: CostUsageCache\n        var touched: Set<String>\n        let range: CostUsageDayRange\n        let providerFilter: ClaudeLogProviderFilter\n\n        init(cache: CostUsageCache, range: CostUsageDayRange, providerFilter: ClaudeLogProviderFilter) {\n            self.cache = cache\n            self.touched = []\n            self.range = range\n            self.providerFilter = providerFilter\n        }\n    }\n\n    private static func processClaudeFile(\n        url: URL,\n        size: Int64,\n        mtimeMs: Int64,\n        state: ClaudeScanState)\n    {\n        let path = url.path\n        state.touched.insert(path)\n\n        if let cached = state.cache.files[path],\n           cached.mtimeUnixMs == mtimeMs,\n           cached.size == size\n        {\n            return\n        }\n\n        if let cached = state.cache.files[path] {\n            let startOffset = cached.parsedBytes ?? cached.size\n            let canIncremental = size > cached.size && startOffset > 0 && startOffset <= size\n            if canIncremental {\n                let delta = Self.parseClaudeFile(\n                    fileURL: url,\n                    range: state.range,\n                    providerFilter: state.providerFilter,\n                    startOffset: startOffset)\n                if !delta.days.isEmpty {\n                    Self.applyFileDays(cache: &state.cache, fileDays: delta.days, sign: 1)\n                }\n\n                var mergedDays = cached.days\n                Self.mergeFileDays(existing: &mergedDays, delta: delta.days)\n                state.cache.files[path] = Self.makeFileUsage(\n                    mtimeUnixMs: mtimeMs,\n                    size: size,\n                    days: mergedDays,\n                    parsedBytes: delta.parsedBytes)\n                return\n            }\n\n            Self.applyFileDays(cache: &state.cache, fileDays: cached.days, sign: -1)\n        }\n\n        let parsed = Self.parseClaudeFile(\n            fileURL: url,\n            range: state.range,\n            providerFilter: state.providerFilter)\n        let usage = Self.makeFileUsage(\n            mtimeUnixMs: mtimeMs,\n            size: size,\n            days: parsed.days,\n            parsedBytes: parsed.parsedBytes)\n        state.cache.files[path] = usage\n        Self.applyFileDays(cache: &state.cache, fileDays: usage.days, sign: 1)\n    }\n\n    private static func scanClaudeRoot(\n        root: URL,\n        state: ClaudeScanState)\n    {\n        let rootPath = root.path\n        let rootCandidates = Self.claudeRootCandidates(for: rootPath)\n        let prefixes = Set(rootCandidates).map { path in\n            path.hasSuffix(\"/\") ? path : \"\\(path)/\"\n        }\n        let rootExists = rootCandidates.contains { FileManager.default.fileExists(atPath: $0) }\n\n        guard rootExists else {\n            let stale = state.cache.files.keys.filter { path in\n                prefixes.contains(where: { path.hasPrefix($0) })\n            }\n            for path in stale {\n                if let old = state.cache.files[path] {\n                    Self.applyFileDays(cache: &state.cache, fileDays: old.days, sign: -1)\n                }\n                state.cache.files.removeValue(forKey: path)\n            }\n            return\n        }\n\n        // Always enumerate the directory tree. The per-file mtime/size cache in\n        // processClaudeFile already skips unchanged files, so the only cost here is\n        // the directory walk itself. The previous root-mtime optimization skipped\n        // enumeration entirely when the root directory mtime was unchanged, but on\n        // POSIX systems a directory mtime only updates for direct child changes —\n        // not for files created or modified inside subdirectories. This caused new\n        // session logs to go undetected until the cache was manually cleared.\n        let keys: [URLResourceKey] = [\n            .isRegularFileKey,\n            .contentModificationDateKey,\n            .fileSizeKey,\n        ]\n\n        guard let enumerator = FileManager.default.enumerator(\n            at: root,\n            includingPropertiesForKeys: keys,\n            options: [.skipsHiddenFiles, .skipsPackageDescendants])\n        else { return }\n\n        for case let url as URL in enumerator {\n            guard url.pathExtension.lowercased() == \"jsonl\" else { continue }\n            guard let values = try? url.resourceValues(forKeys: Set(keys)) else { continue }\n            guard values.isRegularFile == true else { continue }\n            let size = Int64(values.fileSize ?? 0)\n            if size <= 0 { continue }\n\n            let mtime = values.contentModificationDate?.timeIntervalSince1970 ?? 0\n            let mtimeMs = Int64(mtime * 1000)\n            Self.processClaudeFile(\n                url: url,\n                size: size,\n                mtimeMs: mtimeMs,\n                state: state)\n        }\n\n        // Root mtime caching removed — see comment above.\n    }\n\n    static func loadClaudeDaily(\n        provider: UsageProvider,\n        range: CostUsageDayRange,\n        now: Date,\n        options: Options) -> CostUsageDailyReport\n    {\n        var cache = CostUsageCacheIO.load(provider: provider, cacheRoot: options.cacheRoot)\n        let nowMs = Int64(now.timeIntervalSince1970 * 1000)\n\n        let refreshMs = Int64(max(0, options.refreshMinIntervalSeconds) * 1000)\n        let shouldRefresh = refreshMs == 0 || cache.lastScanUnixMs == 0 || nowMs - cache.lastScanUnixMs > refreshMs\n\n        let roots = self.defaultClaudeProjectsRoots(options: options)\n        let providerFilter = options.claudeLogProviderFilter\n\n        var touched: Set<String> = []\n\n        if shouldRefresh {\n            if options.forceRescan {\n                cache = CostUsageCache()\n            }\n            let scanState = ClaudeScanState(cache: cache, range: range, providerFilter: providerFilter)\n\n            for root in roots {\n                Self.scanClaudeRoot(\n                    root: root,\n                    state: scanState)\n            }\n\n            cache = scanState.cache\n            touched = scanState.touched\n            cache.roots = nil\n\n            for key in cache.files.keys where !touched.contains(key) {\n                if let old = cache.files[key] {\n                    Self.applyFileDays(cache: &cache, fileDays: old.days, sign: -1)\n                }\n                cache.files.removeValue(forKey: key)\n            }\n\n            Self.pruneDays(cache: &cache, sinceKey: range.scanSinceKey, untilKey: range.scanUntilKey)\n            cache.lastScanUnixMs = nowMs\n            CostUsageCacheIO.save(provider: provider, cache: cache, cacheRoot: options.cacheRoot)\n        }\n\n        return Self.buildClaudeReportFromCache(cache: cache, range: range)\n    }\n\n    private static func buildClaudeReportFromCache(\n        cache: CostUsageCache,\n        range: CostUsageDayRange) -> CostUsageDailyReport\n    {\n        var entries: [CostUsageDailyReport.Entry] = []\n        var totalInput = 0\n        var totalOutput = 0\n        var totalCacheRead = 0\n        var totalCacheCreate = 0\n        var totalTokens = 0\n        var totalCost: Double = 0\n        var costSeen = false\n        let costScale = 1_000_000_000.0\n\n        let dayKeys = cache.days.keys.sorted().filter {\n            CostUsageDayRange.isInRange(dayKey: $0, since: range.sinceKey, until: range.untilKey)\n        }\n\n        for day in dayKeys {\n            guard let models = cache.days[day] else { continue }\n            let modelNames = models.keys.sorted()\n\n            var dayInput = 0\n            var dayOutput = 0\n            var dayCacheRead = 0\n            var dayCacheCreate = 0\n\n            var breakdown: [CostUsageDailyReport.ModelBreakdown] = []\n            var dayCost: Double = 0\n            var dayCostSeen = false\n\n            for model in modelNames {\n                let packed = models[model] ?? [0, 0, 0, 0]\n                let input = packed[safe: 0] ?? 0\n                let cacheRead = packed[safe: 1] ?? 0\n                let cacheCreate = packed[safe: 2] ?? 0\n                let output = packed[safe: 3] ?? 0\n                let cachedCost = packed[safe: 4] ?? 0\n                let totalTokens = input + cacheRead + cacheCreate + output\n\n                // Cache tokens are tracked separately; totalTokens includes input + cache.\n                dayInput += input\n                dayCacheRead += cacheRead\n                dayCacheCreate += cacheCreate\n                dayOutput += output\n\n                let cost = cachedCost > 0\n                    ? Double(cachedCost) / costScale\n                    : CostUsagePricing.claudeCostUSD(\n                        model: model,\n                        inputTokens: input,\n                        cacheReadInputTokens: cacheRead,\n                        cacheCreationInputTokens: cacheCreate,\n                        outputTokens: output)\n                breakdown.append(\n                    CostUsageDailyReport.ModelBreakdown(\n                        modelName: model,\n                        costUSD: cost,\n                        totalTokens: totalTokens))\n                if let cost {\n                    dayCost += cost\n                    dayCostSeen = true\n                }\n            }\n\n            let sortedBreakdown = Self.sortedModelBreakdowns(breakdown)\n\n            let dayTotal = dayInput + dayCacheRead + dayCacheCreate + dayOutput\n            let entryCost = dayCostSeen ? dayCost : nil\n            entries.append(CostUsageDailyReport.Entry(\n                date: day,\n                inputTokens: dayInput,\n                outputTokens: dayOutput,\n                cacheReadTokens: dayCacheRead,\n                cacheCreationTokens: dayCacheCreate,\n                totalTokens: dayTotal,\n                costUSD: entryCost,\n                modelsUsed: modelNames,\n                modelBreakdowns: sortedBreakdown))\n\n            totalInput += dayInput\n            totalOutput += dayOutput\n            totalCacheRead += dayCacheRead\n            totalCacheCreate += dayCacheCreate\n            totalTokens += dayTotal\n            if let entryCost {\n                totalCost += entryCost\n                costSeen = true\n            }\n        }\n\n        let summary: CostUsageDailyReport.Summary? = entries.isEmpty\n            ? nil\n            : CostUsageDailyReport.Summary(\n                totalInputTokens: totalInput,\n                totalOutputTokens: totalOutput,\n                cacheReadTokens: totalCacheRead,\n                cacheCreationTokens: totalCacheCreate,\n                totalTokens: totalTokens,\n                totalCostUSD: costSeen ? totalCost : nil)\n\n        return CostUsageDailyReport(data: entries, summary: summary)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner+Timestamp.swift",
    "content": "import Foundation\n\nprivate final class CostUsageISO8601FormatterBox: @unchecked Sendable {\n    let lock = NSLock()\n    let withFractional: ISO8601DateFormatter = {\n        let fmt = ISO8601DateFormatter()\n        fmt.formatOptions = [.withInternetDateTime, .withFractionalSeconds]\n        return fmt\n    }()\n\n    let plain: ISO8601DateFormatter = {\n        let fmt = ISO8601DateFormatter()\n        fmt.formatOptions = [.withInternetDateTime]\n        return fmt\n    }()\n}\n\nprivate enum CostUsageTimestampParser {\n    static let box = CostUsageISO8601FormatterBox()\n\n    static func parseISO(_ text: String) -> Date? {\n        self.box.lock.lock()\n        defer { self.box.lock.unlock() }\n        return self.box.withFractional.date(from: text) ?? self.box.plain.date(from: text)\n    }\n}\n\nextension CostUsageScanner {\n    static func dayKeyFromTimestamp(_ text: String) -> String? {\n        let bytes = Array(text.utf8)\n        guard bytes.count >= 20 else { return nil }\n        guard bytes[safe: 4] == 45, bytes[safe: 7] == 45 else { return nil }\n        guard let year = Self.parse4(bytes, at: 0),\n              let month = Self.parse2(bytes, at: 5),\n              let day = Self.parse2(bytes, at: 8) else { return nil }\n\n        var hour = 0\n        var minute = 0\n        var second = 0\n\n        if bytes[safe: 10] == 84 {\n            guard bytes.count >= 19 else { return nil }\n            guard bytes[safe: 13] == 58, bytes[safe: 16] == 58 else { return nil }\n            guard let parsedHour = Self.parse2(bytes, at: 11),\n                  let parsedMinute = Self.parse2(bytes, at: 14),\n                  let parsedSecond = Self.parse2(bytes, at: 17) else { return nil }\n            hour = parsedHour\n            minute = parsedMinute\n            second = parsedSecond\n        }\n\n        var tzIndex: Int?\n        var tzSign = 0\n        for idx in stride(from: bytes.count - 1, through: 11, by: -1) {\n            let byte = bytes[idx]\n            if byte == 90 {\n                tzIndex = idx\n                tzSign = 0\n                break\n            }\n            if byte == 43 {\n                tzIndex = idx\n                tzSign = 1\n                break\n            }\n            if byte == 45 {\n                tzIndex = idx\n                tzSign = -1\n                break\n            }\n        }\n\n        guard let tzStart = tzIndex else { return nil }\n        var offsetSeconds = 0\n        if tzSign != 0 {\n            let offsetStart = tzStart + 1\n            guard let hours = Self.parse2(bytes, at: offsetStart) else { return nil }\n            var minutes = 0\n            if bytes.count > offsetStart + 2 {\n                if bytes[safe: offsetStart + 2] == 58 {\n                    guard let parsedMinutes = Self.parse2(bytes, at: offsetStart + 3) else { return nil }\n                    minutes = parsedMinutes\n                } else if let parsedMinutes = Self.parse2(bytes, at: offsetStart + 2) {\n                    minutes = parsedMinutes\n                }\n            }\n            offsetSeconds = tzSign * ((hours * 3600) + (minutes * 60))\n        }\n\n        var comps = DateComponents()\n        comps.calendar = Calendar(identifier: .gregorian)\n        comps.timeZone = TimeZone(secondsFromGMT: offsetSeconds)\n        comps.year = year\n        comps.month = month\n        comps.day = day\n        comps.hour = hour\n        comps.minute = minute\n        comps.second = second\n\n        guard let date = comps.date else { return nil }\n        let local = Calendar.current.dateComponents([.year, .month, .day], from: date)\n        guard let localYear = local.year,\n              let localMonth = local.month,\n              let localDay = local.day else { return nil }\n        return String(format: \"%04d-%02d-%02d\", localYear, localMonth, localDay)\n    }\n\n    static func dayKeyFromParsedISO(_ text: String) -> String? {\n        guard let date = CostUsageTimestampParser.parseISO(text) else { return nil }\n        return CostUsageDayRange.dayKey(from: date)\n    }\n\n    private static func parse2(_ bytes: [UInt8], at index: Int) -> Int? {\n        guard let d0 = parseDigit(bytes[safe: index]),\n              let d1 = parseDigit(bytes[safe: index + 1]) else { return nil }\n        return d0 * 10 + d1\n    }\n\n    private static func parse4(_ bytes: [UInt8], at index: Int) -> Int? {\n        guard let d0 = parseDigit(bytes[safe: index]),\n              let d1 = parseDigit(bytes[safe: index + 1]),\n              let d2 = parseDigit(bytes[safe: index + 2]),\n              let d3 = parseDigit(bytes[safe: index + 3]) else { return nil }\n        return d0 * 1000 + d1 * 100 + d2 * 10 + d3\n    }\n\n    private static func parseDigit(_ byte: UInt8?) -> Int? {\n        guard let byte else { return nil }\n        guard byte >= 48, byte <= 57 else { return nil }\n        return Int(byte - 48)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift",
    "content": "import Foundation\n\nenum CostUsageScanner {\n    enum ClaudeLogProviderFilter {\n        case all\n        case vertexAIOnly\n        case excludeVertexAI\n    }\n\n    struct Options {\n        var codexSessionsRoot: URL?\n        var claudeProjectsRoots: [URL]?\n        var cacheRoot: URL?\n        var refreshMinIntervalSeconds: TimeInterval = 60\n        var claudeLogProviderFilter: ClaudeLogProviderFilter = .all\n        /// Force a full rescan, ignoring per-file cache and incremental offsets.\n        var forceRescan: Bool = false\n\n        init(\n            codexSessionsRoot: URL? = nil,\n            claudeProjectsRoots: [URL]? = nil,\n            cacheRoot: URL? = nil,\n            claudeLogProviderFilter: ClaudeLogProviderFilter = .all,\n            forceRescan: Bool = false)\n        {\n            self.codexSessionsRoot = codexSessionsRoot\n            self.claudeProjectsRoots = claudeProjectsRoots\n            self.cacheRoot = cacheRoot\n            self.claudeLogProviderFilter = claudeLogProviderFilter\n            self.forceRescan = forceRescan\n        }\n    }\n\n    struct CodexParseResult {\n        let days: [String: [String: [Int]]]\n        let parsedBytes: Int64\n        let lastModel: String?\n        let lastTotals: CostUsageCodexTotals?\n        let sessionId: String?\n    }\n\n    private struct CodexScanState {\n        var seenSessionIds: Set<String> = []\n        var seenFileIds: Set<String> = []\n    }\n\n    struct ClaudeParseResult {\n        let days: [String: [String: [Int]]]\n        let parsedBytes: Int64\n    }\n\n    static func loadDailyReport(\n        provider: UsageProvider,\n        since: Date,\n        until: Date,\n        now: Date = Date(),\n        options: Options = Options()) -> CostUsageDailyReport\n    {\n        let range = CostUsageDayRange(since: since, until: until)\n        let emptyReport = CostUsageDailyReport(data: [], summary: nil)\n\n        switch provider {\n        case .codex:\n            return self.loadCodexDaily(range: range, now: now, options: options)\n        case .claude:\n            return self.loadClaudeDaily(provider: .claude, range: range, now: now, options: options)\n        case .vertexai:\n            var filtered = options\n            if filtered.claudeLogProviderFilter == .all {\n                filtered.claudeLogProviderFilter = .vertexAIOnly\n            }\n            return self.loadClaudeDaily(provider: .vertexai, range: range, now: now, options: filtered)\n        case .zai, .gemini, .antigravity, .cursor, .opencode, .alibaba, .factory, .copilot, .minimax, .kilo,\n             .kiro, .kimi,\n             .kimik2, .augment, .jetbrains, .amp, .ollama, .synthetic, .openrouter, .warp:\n            return emptyReport\n        }\n    }\n\n    // MARK: - Day keys\n\n    struct CostUsageDayRange {\n        let sinceKey: String\n        let untilKey: String\n        let scanSinceKey: String\n        let scanUntilKey: String\n\n        init(since: Date, until: Date) {\n            self.sinceKey = Self.dayKey(from: since)\n            self.untilKey = Self.dayKey(from: until)\n            self.scanSinceKey = Self.dayKey(from: Calendar.current.date(byAdding: .day, value: -1, to: since) ?? since)\n            self.scanUntilKey = Self.dayKey(from: Calendar.current.date(byAdding: .day, value: 1, to: until) ?? until)\n        }\n\n        static func dayKey(from date: Date) -> String {\n            let cal = Calendar.current\n            let comps = cal.dateComponents([.year, .month, .day], from: date)\n            let y = comps.year ?? 1970\n            let m = comps.month ?? 1\n            let d = comps.day ?? 1\n            return String(format: \"%04d-%02d-%02d\", y, m, d)\n        }\n\n        static func isInRange(dayKey: String, since: String, until: String) -> Bool {\n            if dayKey < since { return false }\n            if dayKey > until { return false }\n            return true\n        }\n    }\n\n    // MARK: - Codex\n\n    private static func defaultCodexSessionsRoot(options: Options) -> URL {\n        if let override = options.codexSessionsRoot { return override }\n        let env = ProcessInfo.processInfo.environment[\"CODEX_HOME\"]?.trimmingCharacters(in: .whitespacesAndNewlines)\n        if let env, !env.isEmpty {\n            return URL(fileURLWithPath: env).appendingPathComponent(\"sessions\", isDirectory: true)\n        }\n        return FileManager.default.homeDirectoryForCurrentUser\n            .appendingPathComponent(\".codex\", isDirectory: true)\n            .appendingPathComponent(\"sessions\", isDirectory: true)\n    }\n\n    private static func codexSessionsRoots(options: Options) -> [URL] {\n        let root = self.defaultCodexSessionsRoot(options: options)\n        if let archived = self.codexArchivedSessionsRoot(sessionsRoot: root) {\n            return [root, archived]\n        }\n        return [root]\n    }\n\n    private static func codexArchivedSessionsRoot(sessionsRoot: URL) -> URL? {\n        guard sessionsRoot.lastPathComponent == \"sessions\" else { return nil }\n        return sessionsRoot\n            .deletingLastPathComponent()\n            .appendingPathComponent(\"archived_sessions\", isDirectory: true)\n    }\n\n    private static func listCodexSessionFiles(root: URL, scanSinceKey: String, scanUntilKey: String) -> [URL] {\n        let partitioned = self.listCodexSessionFilesByDatePartition(\n            root: root,\n            scanSinceKey: scanSinceKey,\n            scanUntilKey: scanUntilKey)\n        let flat = self.listCodexSessionFilesFlat(root: root, scanSinceKey: scanSinceKey, scanUntilKey: scanUntilKey)\n        var seen: Set<String> = []\n        var out: [URL] = []\n        for item in partitioned + flat where !seen.contains(item.path) {\n            seen.insert(item.path)\n            out.append(item)\n        }\n        return out\n    }\n\n    private static func listCodexSessionFilesByDatePartition(\n        root: URL,\n        scanSinceKey: String,\n        scanUntilKey: String) -> [URL]\n    {\n        guard FileManager.default.fileExists(atPath: root.path) else { return [] }\n        var out: [URL] = []\n        var date = Self.parseDayKey(scanSinceKey) ?? Date()\n        let untilDate = Self.parseDayKey(scanUntilKey) ?? date\n\n        while date <= untilDate {\n            let comps = Calendar.current.dateComponents([.year, .month, .day], from: date)\n            let y = String(format: \"%04d\", comps.year ?? 1970)\n            let m = String(format: \"%02d\", comps.month ?? 1)\n            let d = String(format: \"%02d\", comps.day ?? 1)\n\n            let dayDir = root.appendingPathComponent(y, isDirectory: true)\n                .appendingPathComponent(m, isDirectory: true)\n                .appendingPathComponent(d, isDirectory: true)\n\n            if let items = try? FileManager.default.contentsOfDirectory(\n                at: dayDir,\n                includingPropertiesForKeys: [.isRegularFileKey],\n                options: [.skipsHiddenFiles])\n            {\n                for item in items where item.pathExtension.lowercased() == \"jsonl\" {\n                    out.append(item)\n                }\n            }\n\n            date = Calendar.current.date(byAdding: .day, value: 1, to: date) ?? untilDate.addingTimeInterval(1)\n        }\n\n        return out\n    }\n\n    private static func listCodexSessionFilesFlat(root: URL, scanSinceKey: String, scanUntilKey: String) -> [URL] {\n        guard FileManager.default.fileExists(atPath: root.path) else { return [] }\n        guard let items = try? FileManager.default.contentsOfDirectory(\n            at: root,\n            includingPropertiesForKeys: [.isRegularFileKey],\n            options: [.skipsHiddenFiles, .skipsPackageDescendants])\n        else { return [] }\n\n        var out: [URL] = []\n        for item in items where item.pathExtension.lowercased() == \"jsonl\" {\n            if let dayKey = Self.dayKeyFromFilename(item.lastPathComponent) {\n                if !CostUsageDayRange.isInRange(dayKey: dayKey, since: scanSinceKey, until: scanUntilKey) {\n                    continue\n                }\n            }\n            out.append(item)\n        }\n        return out\n    }\n\n    private static let codexFilenameDateRegex = try? NSRegularExpression(pattern: \"(\\\\d{4}-\\\\d{2}-\\\\d{2})\")\n\n    private static func dayKeyFromFilename(_ filename: String) -> String? {\n        guard let regex = self.codexFilenameDateRegex else { return nil }\n        let range = NSRange(filename.startIndex..<filename.endIndex, in: filename)\n        guard let match = regex.firstMatch(in: filename, range: range) else { return nil }\n        guard let matchRange = Range(match.range(at: 1), in: filename) else { return nil }\n        return String(filename[matchRange])\n    }\n\n    private static func fileIdentityString(fileURL: URL) -> String? {\n        guard let values = try? fileURL.resourceValues(forKeys: [.fileResourceIdentifierKey]) else { return nil }\n        guard let identifier = values.fileResourceIdentifier else { return nil }\n        if let data = identifier as? Data {\n            return data.base64EncodedString()\n        }\n        return String(describing: identifier)\n    }\n\n    static func parseCodexFile(\n        fileURL: URL,\n        range: CostUsageDayRange,\n        startOffset: Int64 = 0,\n        initialModel: String? = nil,\n        initialTotals: CostUsageCodexTotals? = nil) -> CodexParseResult\n    {\n        var currentModel = initialModel\n        var previousTotals = initialTotals\n        var sessionId: String?\n\n        var days: [String: [String: [Int]]] = [:]\n\n        func add(dayKey: String, model: String, input: Int, cached: Int, output: Int) {\n            guard CostUsageDayRange.isInRange(dayKey: dayKey, since: range.scanSinceKey, until: range.scanUntilKey)\n            else { return }\n            let normModel = CostUsagePricing.normalizeCodexModel(model)\n\n            var dayModels = days[dayKey] ?? [:]\n            var packed = dayModels[normModel] ?? [0, 0, 0]\n            packed[0] = (packed[safe: 0] ?? 0) + input\n            packed[1] = (packed[safe: 1] ?? 0) + cached\n            packed[2] = (packed[safe: 2] ?? 0) + output\n            dayModels[normModel] = packed\n            days[dayKey] = dayModels\n        }\n\n        let maxLineBytes = 256 * 1024\n        let prefixBytes = 32 * 1024\n\n        let parsedBytes = (try? CostUsageJsonl.scan(\n            fileURL: fileURL,\n            offset: startOffset,\n            maxLineBytes: maxLineBytes,\n            prefixBytes: prefixBytes,\n            onLine: { line in\n                guard !line.bytes.isEmpty else { return }\n                guard !line.wasTruncated else { return }\n\n                guard\n                    line.bytes.containsAscii(#\"\"type\":\"event_msg\"\"#)\n                    || line.bytes.containsAscii(#\"\"type\":\"turn_context\"\"#)\n                    || line.bytes.containsAscii(#\"\"type\":\"session_meta\"\"#)\n                else { return }\n\n                if line.bytes.containsAscii(#\"\"type\":\"event_msg\"\"#), !line.bytes.containsAscii(#\"\"token_count\"\"#) {\n                    return\n                }\n\n                guard\n                    let obj = (try? JSONSerialization.jsonObject(with: line.bytes)) as? [String: Any],\n                    let type = obj[\"type\"] as? String\n                else { return }\n\n                if type == \"session_meta\" {\n                    if sessionId == nil {\n                        let payload = obj[\"payload\"] as? [String: Any]\n                        sessionId = payload?[\"session_id\"] as? String\n                            ?? payload?[\"sessionId\"] as? String\n                            ?? payload?[\"id\"] as? String\n                            ?? obj[\"session_id\"] as? String\n                            ?? obj[\"sessionId\"] as? String\n                            ?? obj[\"id\"] as? String\n                    }\n                    return\n                }\n\n                guard let tsText = obj[\"timestamp\"] as? String else { return }\n                guard let dayKey = Self.dayKeyFromTimestamp(tsText) ?? Self.dayKeyFromParsedISO(tsText) else { return }\n\n                if type == \"turn_context\" {\n                    if let payload = obj[\"payload\"] as? [String: Any] {\n                        if let model = payload[\"model\"] as? String {\n                            currentModel = model\n                        } else if let info = payload[\"info\"] as? [String: Any], let model = info[\"model\"] as? String {\n                            currentModel = model\n                        }\n                    }\n                    return\n                }\n\n                guard type == \"event_msg\" else { return }\n                guard let payload = obj[\"payload\"] as? [String: Any] else { return }\n                guard (payload[\"type\"] as? String) == \"token_count\" else { return }\n\n                let info = payload[\"info\"] as? [String: Any]\n                let modelFromInfo = info?[\"model\"] as? String\n                    ?? info?[\"model_name\"] as? String\n                    ?? payload[\"model\"] as? String\n                    ?? obj[\"model\"] as? String\n                let model = modelFromInfo ?? currentModel ?? \"gpt-5\"\n\n                func toInt(_ v: Any?) -> Int {\n                    if let n = v as? NSNumber { return n.intValue }\n                    return 0\n                }\n\n                let total = (info?[\"total_token_usage\"] as? [String: Any])\n                let last = (info?[\"last_token_usage\"] as? [String: Any])\n\n                var deltaInput = 0\n                var deltaCached = 0\n                var deltaOutput = 0\n\n                if let total {\n                    let input = toInt(total[\"input_tokens\"])\n                    let cached = toInt(total[\"cached_input_tokens\"] ?? total[\"cache_read_input_tokens\"])\n                    let output = toInt(total[\"output_tokens\"])\n\n                    let prev = previousTotals\n                    deltaInput = max(0, input - (prev?.input ?? 0))\n                    deltaCached = max(0, cached - (prev?.cached ?? 0))\n                    deltaOutput = max(0, output - (prev?.output ?? 0))\n                    previousTotals = CostUsageCodexTotals(input: input, cached: cached, output: output)\n                } else if let last {\n                    deltaInput = max(0, toInt(last[\"input_tokens\"]))\n                    deltaCached = max(0, toInt(last[\"cached_input_tokens\"] ?? last[\"cache_read_input_tokens\"]))\n                    deltaOutput = max(0, toInt(last[\"output_tokens\"]))\n                } else {\n                    return\n                }\n\n                if deltaInput == 0, deltaCached == 0, deltaOutput == 0 { return }\n                let cachedClamp = min(deltaCached, deltaInput)\n                add(dayKey: dayKey, model: model, input: deltaInput, cached: cachedClamp, output: deltaOutput)\n            })) ?? startOffset\n\n        return CodexParseResult(\n            days: days,\n            parsedBytes: parsedBytes,\n            lastModel: currentModel,\n            lastTotals: previousTotals,\n            sessionId: sessionId)\n    }\n\n    private static func scanCodexFile(\n        fileURL: URL,\n        range: CostUsageDayRange,\n        cache: inout CostUsageCache,\n        state: inout CodexScanState)\n    {\n        let path = fileURL.path\n        let attrs = (try? FileManager.default.attributesOfItem(atPath: path)) ?? [:]\n        let mtime = (attrs[.modificationDate] as? Date)?.timeIntervalSince1970 ?? 0\n        let size = (attrs[.size] as? NSNumber)?.int64Value ?? 0\n        let mtimeMs = Int64(mtime * 1000)\n        let fileId = Self.fileIdentityString(fileURL: fileURL)\n\n        func dropCachedFile(_ cached: CostUsageFileUsage?) {\n            if let cached {\n                Self.applyFileDays(cache: &cache, fileDays: cached.days, sign: -1)\n            }\n            cache.files.removeValue(forKey: path)\n        }\n\n        if let fileId, state.seenFileIds.contains(fileId) {\n            dropCachedFile(cache.files[path])\n            return\n        }\n\n        let cached = cache.files[path]\n        if let cachedSessionId = cached?.sessionId, state.seenSessionIds.contains(cachedSessionId) {\n            dropCachedFile(cached)\n            return\n        }\n\n        let needsSessionId = cached != nil && cached?.sessionId == nil\n        if let cached,\n           cached.mtimeUnixMs == mtimeMs,\n           cached.size == size,\n           !needsSessionId\n        {\n            if let cachedSessionId = cached.sessionId {\n                state.seenSessionIds.insert(cachedSessionId)\n            }\n            if let fileId {\n                state.seenFileIds.insert(fileId)\n            }\n            return\n        }\n\n        if let cached, cached.sessionId != nil {\n            let startOffset = cached.parsedBytes ?? cached.size\n            let canIncremental = size > cached.size && startOffset > 0 && startOffset <= size\n                && cached.lastTotals != nil\n            if canIncremental {\n                let delta = Self.parseCodexFile(\n                    fileURL: fileURL,\n                    range: range,\n                    startOffset: startOffset,\n                    initialModel: cached.lastModel,\n                    initialTotals: cached.lastTotals)\n                let sessionId = delta.sessionId ?? cached.sessionId\n                if let sessionId, state.seenSessionIds.contains(sessionId) {\n                    dropCachedFile(cached)\n                    return\n                }\n\n                if !delta.days.isEmpty {\n                    Self.applyFileDays(cache: &cache, fileDays: delta.days, sign: 1)\n                }\n\n                var mergedDays = cached.days\n                Self.mergeFileDays(existing: &mergedDays, delta: delta.days)\n                cache.files[path] = Self.makeFileUsage(\n                    mtimeUnixMs: mtimeMs,\n                    size: size,\n                    days: mergedDays,\n                    parsedBytes: delta.parsedBytes,\n                    lastModel: delta.lastModel,\n                    lastTotals: delta.lastTotals,\n                    sessionId: sessionId)\n                if let sessionId {\n                    state.seenSessionIds.insert(sessionId)\n                }\n                if let fileId {\n                    state.seenFileIds.insert(fileId)\n                }\n                return\n            }\n        }\n\n        if let cached {\n            Self.applyFileDays(cache: &cache, fileDays: cached.days, sign: -1)\n        }\n\n        let parsed = Self.parseCodexFile(fileURL: fileURL, range: range)\n        let sessionId = parsed.sessionId ?? cached?.sessionId\n        if let sessionId, state.seenSessionIds.contains(sessionId) {\n            cache.files.removeValue(forKey: path)\n            return\n        }\n\n        let usage = Self.makeFileUsage(\n            mtimeUnixMs: mtimeMs,\n            size: size,\n            days: parsed.days,\n            parsedBytes: parsed.parsedBytes,\n            lastModel: parsed.lastModel,\n            lastTotals: parsed.lastTotals,\n            sessionId: sessionId)\n        cache.files[path] = usage\n        Self.applyFileDays(cache: &cache, fileDays: usage.days, sign: 1)\n        if let sessionId {\n            state.seenSessionIds.insert(sessionId)\n        }\n        if let fileId {\n            state.seenFileIds.insert(fileId)\n        }\n    }\n\n    private static func loadCodexDaily(range: CostUsageDayRange, now: Date, options: Options) -> CostUsageDailyReport {\n        var cache = CostUsageCacheIO.load(provider: .codex, cacheRoot: options.cacheRoot)\n        let nowMs = Int64(now.timeIntervalSince1970 * 1000)\n\n        let refreshMs = Int64(max(0, options.refreshMinIntervalSeconds) * 1000)\n        let shouldRefresh = refreshMs == 0 || cache.lastScanUnixMs == 0 || nowMs - cache.lastScanUnixMs > refreshMs\n\n        let roots = self.codexSessionsRoots(options: options)\n        var seenPaths: Set<String> = []\n        var files: [URL] = []\n        for root in roots {\n            let rootFiles = Self.listCodexSessionFiles(\n                root: root,\n                scanSinceKey: range.scanSinceKey,\n                scanUntilKey: range.scanUntilKey)\n            for fileURL in rootFiles.sorted(by: { $0.path < $1.path }) where !seenPaths.contains(fileURL.path) {\n                seenPaths.insert(fileURL.path)\n                files.append(fileURL)\n            }\n        }\n        let filePathsInScan = Set(files.map(\\.path))\n\n        if shouldRefresh {\n            if options.forceRescan {\n                cache = CostUsageCache()\n            }\n            var scanState = CodexScanState()\n            for fileURL in files {\n                Self.scanCodexFile(\n                    fileURL: fileURL,\n                    range: range,\n                    cache: &cache,\n                    state: &scanState)\n            }\n\n            for key in cache.files.keys where !filePathsInScan.contains(key) {\n                if let old = cache.files[key] {\n                    Self.applyFileDays(cache: &cache, fileDays: old.days, sign: -1)\n                }\n                cache.files.removeValue(forKey: key)\n            }\n\n            Self.pruneDays(cache: &cache, sinceKey: range.scanSinceKey, untilKey: range.scanUntilKey)\n            cache.lastScanUnixMs = nowMs\n            CostUsageCacheIO.save(provider: .codex, cache: cache, cacheRoot: options.cacheRoot)\n        }\n\n        return Self.buildCodexReportFromCache(cache: cache, range: range)\n    }\n\n    private static func buildCodexReportFromCache(\n        cache: CostUsageCache,\n        range: CostUsageDayRange) -> CostUsageDailyReport\n    {\n        var entries: [CostUsageDailyReport.Entry] = []\n        var totalInput = 0\n        var totalOutput = 0\n        var totalTokens = 0\n        var totalCost: Double = 0\n        var costSeen = false\n\n        let dayKeys = cache.days.keys.sorted().filter {\n            CostUsageDayRange.isInRange(dayKey: $0, since: range.sinceKey, until: range.untilKey)\n        }\n\n        for day in dayKeys {\n            guard let models = cache.days[day] else { continue }\n            let modelNames = models.keys.sorted()\n\n            var dayInput = 0\n            var dayOutput = 0\n\n            var breakdown: [CostUsageDailyReport.ModelBreakdown] = []\n            var dayCost: Double = 0\n            var dayCostSeen = false\n\n            for model in modelNames {\n                let packed = models[model] ?? [0, 0, 0]\n                let input = packed[safe: 0] ?? 0\n                let cached = packed[safe: 1] ?? 0\n                let output = packed[safe: 2] ?? 0\n                let totalTokens = input + output\n\n                dayInput += input\n                dayOutput += output\n\n                let cost = CostUsagePricing.codexCostUSD(\n                    model: model,\n                    inputTokens: input,\n                    cachedInputTokens: cached,\n                    outputTokens: output)\n                breakdown.append(\n                    CostUsageDailyReport.ModelBreakdown(\n                        modelName: model,\n                        costUSD: cost,\n                        totalTokens: totalTokens))\n                if let cost {\n                    dayCost += cost\n                    dayCostSeen = true\n                }\n            }\n\n            let sortedBreakdown = Self.sortedModelBreakdowns(breakdown)\n\n            let dayTotal = dayInput + dayOutput\n            let entryCost = dayCostSeen ? dayCost : nil\n            entries.append(CostUsageDailyReport.Entry(\n                date: day,\n                inputTokens: dayInput,\n                outputTokens: dayOutput,\n                totalTokens: dayTotal,\n                costUSD: entryCost,\n                modelsUsed: modelNames,\n                modelBreakdowns: sortedBreakdown))\n\n            totalInput += dayInput\n            totalOutput += dayOutput\n            totalTokens += dayTotal\n            if let entryCost {\n                totalCost += entryCost\n                costSeen = true\n            }\n        }\n\n        let summary: CostUsageDailyReport.Summary? = entries.isEmpty\n            ? nil\n            : CostUsageDailyReport.Summary(\n                totalInputTokens: totalInput,\n                totalOutputTokens: totalOutput,\n                totalTokens: totalTokens,\n                totalCostUSD: costSeen ? totalCost : nil)\n\n        return CostUsageDailyReport(data: entries, summary: summary)\n    }\n\n    // MARK: - Shared cache mutations\n\n    static func makeFileUsage(\n        mtimeUnixMs: Int64,\n        size: Int64,\n        days: [String: [String: [Int]]],\n        parsedBytes: Int64?,\n        lastModel: String? = nil,\n        lastTotals: CostUsageCodexTotals? = nil,\n        sessionId: String? = nil) -> CostUsageFileUsage\n    {\n        CostUsageFileUsage(\n            mtimeUnixMs: mtimeUnixMs,\n            size: size,\n            days: days,\n            parsedBytes: parsedBytes,\n            lastModel: lastModel,\n            lastTotals: lastTotals,\n            sessionId: sessionId)\n    }\n\n    static func mergeFileDays(\n        existing: inout [String: [String: [Int]]],\n        delta: [String: [String: [Int]]])\n    {\n        for (day, models) in delta {\n            var dayModels = existing[day] ?? [:]\n            for (model, packed) in models {\n                let existingPacked = dayModels[model] ?? []\n                let merged = Self.addPacked(a: existingPacked, b: packed, sign: 1)\n                if merged.allSatisfy({ $0 == 0 }) {\n                    dayModels.removeValue(forKey: model)\n                } else {\n                    dayModels[model] = merged\n                }\n            }\n\n            if dayModels.isEmpty {\n                existing.removeValue(forKey: day)\n            } else {\n                existing[day] = dayModels\n            }\n        }\n    }\n\n    static func applyFileDays(cache: inout CostUsageCache, fileDays: [String: [String: [Int]]], sign: Int) {\n        for (day, models) in fileDays {\n            var dayModels = cache.days[day] ?? [:]\n            for (model, packed) in models {\n                let existing = dayModels[model] ?? []\n                let merged = Self.addPacked(a: existing, b: packed, sign: sign)\n                if merged.allSatisfy({ $0 == 0 }) {\n                    dayModels.removeValue(forKey: model)\n                } else {\n                    dayModels[model] = merged\n                }\n            }\n\n            if dayModels.isEmpty {\n                cache.days.removeValue(forKey: day)\n            } else {\n                cache.days[day] = dayModels\n            }\n        }\n    }\n\n    static func pruneDays(cache: inout CostUsageCache, sinceKey: String, untilKey: String) {\n        for key in cache.days.keys where !CostUsageDayRange.isInRange(dayKey: key, since: sinceKey, until: untilKey) {\n            cache.days.removeValue(forKey: key)\n        }\n    }\n\n    static func addPacked(a: [Int], b: [Int], sign: Int) -> [Int] {\n        let len = max(a.count, b.count)\n        var out: [Int] = Array(repeating: 0, count: len)\n        for idx in 0..<len {\n            let next = (a[safe: idx] ?? 0) + sign * (b[safe: idx] ?? 0)\n            out[idx] = max(0, next)\n        }\n        return out\n    }\n\n    static func sortedModelBreakdowns(_ breakdowns: [CostUsageDailyReport.ModelBreakdown])\n        -> [CostUsageDailyReport.ModelBreakdown]\n    {\n        breakdowns.sorted { lhs, rhs in\n            let lhsCost = lhs.costUSD ?? -1\n            let rhsCost = rhs.costUSD ?? -1\n            if lhsCost != rhsCost {\n                return lhsCost > rhsCost\n            }\n\n            let lhsTokens = lhs.totalTokens ?? -1\n            let rhsTokens = rhs.totalTokens ?? -1\n            if lhsTokens != rhsTokens {\n                return lhsTokens > rhsTokens\n            }\n\n            return lhs.modelName > rhs.modelName\n        }\n    }\n\n    // MARK: - Date parsing\n\n    private static func parseDayKey(_ key: String) -> Date? {\n        let parts = key.split(separator: \"-\")\n        guard parts.count == 3 else { return nil }\n        guard\n            let y = Int(parts[0]),\n            let m = Int(parts[1]),\n            let d = Int(parts[2])\n        else { return nil }\n\n        var comps = DateComponents()\n        comps.calendar = Calendar.current\n        comps.timeZone = TimeZone.current\n        comps.year = y\n        comps.month = m\n        comps.day = d\n        comps.hour = 12\n        return comps.date\n    }\n}\n\nextension Data {\n    func containsAscii(_ needle: String) -> Bool {\n        guard let n = needle.data(using: .utf8) else { return false }\n        return self.range(of: n) != nil\n    }\n}\n\nextension [Int] {\n    subscript(safe index: Int) -> Int? {\n        if index < 0 { return nil }\n        if index >= self.count { return nil }\n        return self[index]\n    }\n}\n\nextension [UInt8] {\n    subscript(safe index: Int) -> UInt8? {\n        if index < 0 { return nil }\n        if index >= self.count { return nil }\n        return self[index]\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarCore/WebKit/WebKitTeardown.swift",
    "content": "#if os(macOS)\nimport AppKit\nimport Foundation\nimport WebKit\n\n@MainActor\npublic enum WebKitTeardown {\n    private static var retained: [ObjectIdentifier: AnyObject] = [:]\n    private static var scheduled: Set<ObjectIdentifier> = []\n    private static let log = CodexBarLog.logger(LogCategories.webkitTeardown)\n\n    #if arch(x86_64)\n    private static let retainAfterCleanup = true\n    #else\n    private static let retainAfterCleanup = false\n    #endif\n    private static let cleanupDelay: TimeInterval = 0.25\n    private static let releasePollInterval: TimeInterval = 0.2\n    private static let releaseMinimumDelay: TimeInterval = 2.0\n    #if arch(x86_64)\n    private static let releaseMaximumDelay: TimeInterval = 8.0\n    #else\n    private static let releaseMaximumDelay: TimeInterval = 2.0\n    #endif\n\n    public static func retain(_ owner: AnyObject) {\n        self.retained[ObjectIdentifier(owner)] = owner\n    }\n\n    public static func scheduleCleanup(\n        owner: AnyObject,\n        window: NSWindow?,\n        webView: WKWebView?,\n        closeWindow: (() -> Void)? = nil)\n    {\n        let id = ObjectIdentifier(owner)\n        self.retained[id] = owner\n        guard !self.scheduled.contains(id) else { return }\n        self.scheduled.insert(id)\n        self.log.debug(\n            \"WebKit cleanup scheduled\",\n            metadata: [\"window\": \"\\(window != nil)\", \"webView\": \"\\(webView != nil)\"])\n\n        Task { @MainActor in\n            // Let WebKit unwind delegate callbacks before teardown on Intel.\n            await Task.yield()\n            try? await Task.sleep(nanoseconds: self.nanoseconds(self.cleanupDelay))\n            self.cleanup(window: window, webView: webView, closeWindow: closeWindow)\n            self.scheduleRelease(id: id, window: window, webView: webView)\n        }\n    }\n\n    #if DEBUG\n    static func isRetainedForTesting(_ owner: AnyObject) -> Bool {\n        self.retained[ObjectIdentifier(owner)] != nil\n    }\n\n    static func isScheduledForTesting(_ owner: AnyObject) -> Bool {\n        self.scheduled.contains(ObjectIdentifier(owner))\n    }\n\n    static func resetForTesting() {\n        self.retained.removeAll()\n        self.scheduled.removeAll()\n    }\n    #endif\n\n    private static func cleanup(window: NSWindow?, webView: WKWebView?, closeWindow: (() -> Void)?) {\n        self.log.debug(\n            \"WebKit cleanup\",\n            metadata: [\"window\": \"\\(window != nil)\", \"webView\": \"\\(webView != nil)\"])\n        webView?.stopLoading()\n        webView?.navigationDelegate = nil\n        window?.delegate = nil\n\n        if self.retainAfterCleanup {\n            window?.orderOut(nil)\n        } else if let closeWindow {\n            closeWindow()\n        } else {\n            window?.close()\n        }\n    }\n\n    private static func scheduleRelease(id: ObjectIdentifier, window: NSWindow?, webView: WKWebView?) {\n        Task { @MainActor in\n            let start = Date()\n            while true {\n                let elapsed = Date().timeIntervalSince(start)\n                let windowVisible = window?.isVisible ?? false\n                let webViewLoading = webView?.isLoading ?? false\n                let inPlay = windowVisible || webViewLoading\n                if elapsed >= self.releaseMaximumDelay || (!inPlay && elapsed >= self.releaseMinimumDelay) {\n                    break\n                }\n                try? await Task.sleep(nanoseconds: self.nanoseconds(self.releasePollInterval))\n            }\n            self.retained.removeValue(forKey: id)\n            self.scheduled.remove(id)\n            self.log.debug(\"WebKit cleanup released\")\n        }\n    }\n\n    private static func nanoseconds(_ interval: TimeInterval) -> UInt64 {\n        UInt64(interval * 1_000_000_000)\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/CodexBarCore/WidgetSnapshot.swift",
    "content": "import Foundation\n\npublic struct WidgetSnapshot: Codable, Sendable {\n    public struct ProviderEntry: Codable, Sendable {\n        public let provider: UsageProvider\n        public let updatedAt: Date\n        public let primary: RateWindow?\n        public let secondary: RateWindow?\n        public let tertiary: RateWindow?\n        public let creditsRemaining: Double?\n        public let codeReviewRemainingPercent: Double?\n        public let tokenUsage: TokenUsageSummary?\n        public let dailyUsage: [DailyUsagePoint]\n\n        public init(\n            provider: UsageProvider,\n            updatedAt: Date,\n            primary: RateWindow?,\n            secondary: RateWindow?,\n            tertiary: RateWindow?,\n            creditsRemaining: Double?,\n            codeReviewRemainingPercent: Double?,\n            tokenUsage: TokenUsageSummary?,\n            dailyUsage: [DailyUsagePoint])\n        {\n            self.provider = provider\n            self.updatedAt = updatedAt\n            self.primary = primary\n            self.secondary = secondary\n            self.tertiary = tertiary\n            self.creditsRemaining = creditsRemaining\n            self.codeReviewRemainingPercent = codeReviewRemainingPercent\n            self.tokenUsage = tokenUsage\n            self.dailyUsage = dailyUsage\n        }\n    }\n\n    public struct TokenUsageSummary: Codable, Sendable {\n        public let sessionCostUSD: Double?\n        public let sessionTokens: Int?\n        public let last30DaysCostUSD: Double?\n        public let last30DaysTokens: Int?\n\n        public init(\n            sessionCostUSD: Double?,\n            sessionTokens: Int?,\n            last30DaysCostUSD: Double?,\n            last30DaysTokens: Int?)\n        {\n            self.sessionCostUSD = sessionCostUSD\n            self.sessionTokens = sessionTokens\n            self.last30DaysCostUSD = last30DaysCostUSD\n            self.last30DaysTokens = last30DaysTokens\n        }\n    }\n\n    public struct DailyUsagePoint: Codable, Sendable {\n        public let dayKey: String\n        public let totalTokens: Int?\n        public let costUSD: Double?\n\n        public init(dayKey: String, totalTokens: Int?, costUSD: Double?) {\n            self.dayKey = dayKey\n            self.totalTokens = totalTokens\n            self.costUSD = costUSD\n        }\n    }\n\n    public let entries: [ProviderEntry]\n    public let enabledProviders: [UsageProvider]\n    public let generatedAt: Date\n\n    public init(entries: [ProviderEntry], enabledProviders: [UsageProvider]? = nil, generatedAt: Date) {\n        self.entries = entries\n        self.enabledProviders = enabledProviders ?? entries.map(\\.provider)\n        self.generatedAt = generatedAt\n    }\n\n    private enum CodingKeys: String, CodingKey {\n        case entries\n        case enabledProviders\n        case generatedAt\n    }\n\n    public init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        self.entries = try container.decode([ProviderEntry].self, forKey: .entries)\n        self.generatedAt = try container.decode(Date.self, forKey: .generatedAt)\n        self.enabledProviders = try container.decodeIfPresent([UsageProvider].self, forKey: .enabledProviders)\n            ?? self.entries.map(\\.provider)\n    }\n\n    public func encode(to encoder: Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        try container.encode(self.entries, forKey: .entries)\n        try container.encode(self.enabledProviders, forKey: .enabledProviders)\n        try container.encode(self.generatedAt, forKey: .generatedAt)\n    }\n}\n\npublic enum WidgetSnapshotStore {\n    public static let appGroupID = \"group.com.steipete.codexbar\"\n    private static let filename = \"widget-snapshot.json\"\n\n    public static func load(bundleID: String? = Bundle.main.bundleIdentifier) -> WidgetSnapshot? {\n        guard let url = self.snapshotURL(bundleID: bundleID) else { return nil }\n        guard let data = try? Data(contentsOf: url) else { return nil }\n        return try? self.decoder.decode(WidgetSnapshot.self, from: data)\n    }\n\n    public static func save(_ snapshot: WidgetSnapshot, bundleID: String? = Bundle.main.bundleIdentifier) {\n        guard let url = self.snapshotURL(bundleID: bundleID) else { return }\n        do {\n            let data = try self.encoder.encode(snapshot)\n            try data.write(to: url, options: [.atomic])\n        } catch {\n            return\n        }\n    }\n\n    private static func snapshotURL(bundleID: String?) -> URL? {\n        let fm = FileManager.default\n        let groupID = self.groupID(for: bundleID)\n        #if os(macOS)\n        if let groupID, let container = fm.containerURL(forSecurityApplicationGroupIdentifier: groupID) {\n            return container.appendingPathComponent(self.filename, isDirectory: false)\n        }\n        #endif\n\n        let base = fm.urls(for: .applicationSupportDirectory, in: .userDomainMask).first\n            ?? fm.temporaryDirectory\n        let dir = base.appendingPathComponent(\"CodexBar\", isDirectory: true)\n        try? fm.createDirectory(at: dir, withIntermediateDirectories: true)\n        return dir.appendingPathComponent(self.filename, isDirectory: false)\n    }\n\n    public static func appGroupID(for bundleID: String?) -> String? {\n        self.groupID(for: bundleID)\n    }\n\n    private static func groupID(for bundleID: String?) -> String? {\n        guard let bundleID, !bundleID.isEmpty else { return self.appGroupID }\n        if bundleID.contains(\".debug\") {\n            return \"group.com.steipete.codexbar.debug\"\n        }\n        return self.appGroupID\n    }\n\n    private static var encoder: JSONEncoder {\n        let encoder = JSONEncoder()\n        encoder.dateEncodingStrategy = .iso8601\n        return encoder\n    }\n\n    private static var decoder: JSONDecoder {\n        let decoder = JSONDecoder()\n        decoder.dateDecodingStrategy = .iso8601\n        return decoder\n    }\n}\n\npublic enum WidgetSelectionStore {\n    private static let selectedProviderKey = \"widgetSelectedProvider\"\n\n    public static func loadSelectedProvider(bundleID: String? = Bundle.main.bundleIdentifier) -> UsageProvider? {\n        guard let defaults = self.sharedDefaults(bundleID: bundleID) else { return nil }\n        guard let raw = defaults.string(forKey: self.selectedProviderKey) else { return nil }\n        return UsageProvider(rawValue: raw)\n    }\n\n    public static func saveSelectedProvider(\n        _ provider: UsageProvider,\n        bundleID: String? = Bundle.main.bundleIdentifier)\n    {\n        guard let defaults = self.sharedDefaults(bundleID: bundleID) else { return }\n        defaults.set(provider.rawValue, forKey: self.selectedProviderKey)\n    }\n\n    private static func sharedDefaults(bundleID: String?) -> UserDefaults? {\n        guard let groupID = WidgetSnapshotStore.appGroupID(for: bundleID) else { return nil }\n        return UserDefaults(suiteName: groupID)\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarMacroSupport/ProviderRegistrationMacros.swift",
    "content": "@attached(peer, names: prefixed(_CodexBarDescriptorRegistration_))\npublic macro ProviderDescriptorRegistration() = #externalMacro(\n    module: \"CodexBarMacros\",\n    type: \"ProviderDescriptorRegistrationMacro\")\n\n@attached(member, names: named(descriptor))\npublic macro ProviderDescriptorDefinition() = #externalMacro(\n    module: \"CodexBarMacros\",\n    type: \"ProviderDescriptorDefinitionMacro\")\n\n@attached(peer, names: prefixed(_CodexBarImplementationRegistration_))\npublic macro ProviderImplementationRegistration() = #externalMacro(\n    module: \"CodexBarMacros\",\n    type: \"ProviderImplementationRegistrationMacro\")\n"
  },
  {
    "path": "Sources/CodexBarMacros/ProviderRegistrationMacros.swift",
    "content": "import SwiftCompilerPlugin\nimport SwiftDiagnostics\nimport SwiftSyntax\nimport SwiftSyntaxBuilder\nimport SwiftSyntaxMacros\n\nprivate enum ProviderMacroError {\n    struct Message: DiagnosticMessage {\n        let message: String\n        let diagnosticID: MessageID\n        let severity: DiagnosticSeverity\n    }\n\n    static func unsupportedTarget(_ context: some MacroExpansionContext, node: SyntaxProtocol, macro: String) {\n        context.diagnose(Diagnostic(\n            node: node,\n            message: Message(\n                message: \"@\\(macro) must be attached to a struct, class, or enum.\",\n                diagnosticID: MessageID(domain: \"CodexBarMacros\", id: \"unsupported_target\"),\n                severity: .error)))\n    }\n\n    static func missingDescriptor(_ context: some MacroExpansionContext, node: SyntaxProtocol, typeName: String) {\n        context.diagnose(Diagnostic(\n            node: node,\n            message: Message(\n                message: \"\\(typeName) must declare static let descriptor or static func makeDescriptor() \" +\n                    \"to use @ProviderDescriptorRegistration.\",\n                diagnosticID: MessageID(domain: \"CodexBarMacros\", id: \"missing_descriptor\"),\n                severity: .error)))\n    }\n\n    static func missingMakeDescriptor(_ context: some MacroExpansionContext, node: SyntaxProtocol, typeName: String) {\n        context.diagnose(Diagnostic(\n            node: node,\n            message: Message(\n                message: \"\\(typeName) must declare static func makeDescriptor() to use @ProviderDescriptorDefinition.\",\n                diagnosticID: MessageID(domain: \"CodexBarMacros\", id: \"missing_make_descriptor\"),\n                severity: .error)))\n    }\n\n    static func duplicateDescriptor(_ context: some MacroExpansionContext, node: SyntaxProtocol, typeName: String) {\n        context.diagnose(Diagnostic(\n            node: node,\n            message: Message(\n                message: \"\\(typeName) already declares descriptor; remove @ProviderDescriptorDefinition.\",\n                diagnosticID: MessageID(domain: \"CodexBarMacros\", id: \"duplicate_descriptor\"),\n                severity: .error)))\n    }\n\n    static func missingInit(_ context: some MacroExpansionContext, node: SyntaxProtocol, typeName: String) {\n        context.diagnose(Diagnostic(\n            node: node,\n            message: Message(\n                message: \"\\(typeName) must provide an init() to use @ProviderImplementationRegistration.\",\n                diagnosticID: MessageID(domain: \"CodexBarMacros\", id: \"missing_init\"),\n                severity: .error)))\n    }\n}\n\nprivate enum ProviderMacroIntrospection {\n    static func typeDecl(from declaration: some DeclSyntaxProtocol) -> (decl: DeclGroupSyntax, name: String)? {\n        if let decl = declaration.as(StructDeclSyntax.self) { return (decl, decl.name.text) }\n        if let decl = declaration.as(ClassDeclSyntax.self) { return (decl, decl.name.text) }\n        if let decl = declaration.as(EnumDeclSyntax.self) { return (decl, decl.name.text) }\n        return nil\n    }\n\n    static func hasStaticDescriptor(in decl: DeclGroupSyntax) -> Bool {\n        for member in decl.memberBlock.members {\n            guard let varDecl = member.decl.as(VariableDeclSyntax.self) else { continue }\n            guard self.isStatic(varDecl.modifiers) else { continue }\n            for binding in varDecl.bindings {\n                guard let pattern = binding.pattern.as(IdentifierPatternSyntax.self) else { continue }\n                if pattern.identifier.text == \"descriptor\" { return true }\n            }\n        }\n        return false\n    }\n\n    static func hasMakeDescriptor(in decl: DeclGroupSyntax) -> Bool {\n        for member in decl.memberBlock.members {\n            guard let funcDecl = member.decl.as(FunctionDeclSyntax.self) else { continue }\n            guard self.isStatic(funcDecl.modifiers) else { continue }\n            if funcDecl.name.text == \"makeDescriptor\" { return true }\n        }\n        return false\n    }\n\n    static func hasAccessibleInit(in decl: DeclGroupSyntax) -> Bool {\n        if self.hasZeroArgInit(in: decl) { return true }\n        if decl.is(EnumDeclSyntax.self) { return false }\n        return self.canSynthesizeDefaultInit(in: decl)\n    }\n\n    private static func hasZeroArgInit(in decl: DeclGroupSyntax) -> Bool {\n        for member in decl.memberBlock.members {\n            guard let initDecl = member.decl.as(InitializerDeclSyntax.self) else { continue }\n            let params = initDecl.signature.parameterClause.parameters\n            if params.isEmpty { return true }\n            let allDefaulted = params.allSatisfy { $0.defaultValue != nil }\n            if allDefaulted { return true }\n        }\n        return false\n    }\n\n    private static func canSynthesizeDefaultInit(in decl: DeclGroupSyntax) -> Bool {\n        for member in decl.memberBlock.members {\n            guard let varDecl = member.decl.as(VariableDeclSyntax.self) else { continue }\n            guard !self.isStatic(varDecl.modifiers) else { continue }\n            for binding in varDecl.bindings {\n                if binding.accessorBlock != nil { continue }\n                if binding.initializer == nil { return false }\n            }\n        }\n        return true\n    }\n\n    private static func isStatic(_ modifiers: DeclModifierListSyntax?) -> Bool {\n        guard let modifiers else { return false }\n        return modifiers.contains { $0.name.tokenKind == .keyword(.static) }\n    }\n}\n\npublic struct ProviderDescriptorRegistrationMacro: PeerMacro {\n    public static func expansion(\n        of _: AttributeSyntax,\n        providingPeersOf declaration: some DeclSyntaxProtocol,\n        in context: some MacroExpansionContext) throws -> [DeclSyntax]\n    {\n        guard let (decl, typeName) = ProviderMacroIntrospection.typeDecl(from: declaration) else {\n            ProviderMacroError.unsupportedTarget(\n                context,\n                node: Syntax(declaration),\n                macro: \"ProviderDescriptorRegistration\")\n            return []\n        }\n\n        let hasDescriptor = ProviderMacroIntrospection.hasStaticDescriptor(in: decl)\n        let hasMakeDescriptor = ProviderMacroIntrospection.hasMakeDescriptor(in: decl)\n        guard hasDescriptor || hasMakeDescriptor else {\n            ProviderMacroError.missingDescriptor(context, node: Syntax(declaration), typeName: typeName)\n            return []\n        }\n\n        let registerName = \"_CodexBarDescriptorRegistration_\\(typeName)\"\n        return [\n            DeclSyntax(\n                \"private let \\(raw: registerName) = ProviderDescriptorRegistry.register(\\(raw: typeName).descriptor)\"),\n        ]\n    }\n}\n\npublic struct ProviderDescriptorDefinitionMacro: MemberMacro {\n    public static func expansion(\n        of _: AttributeSyntax,\n        providingMembersOf declaration: some DeclGroupSyntax,\n        in context: some MacroExpansionContext) throws -> [DeclSyntax]\n    {\n        guard let (decl, typeName) = ProviderMacroIntrospection.typeDecl(from: declaration) else {\n            ProviderMacroError.unsupportedTarget(\n                context,\n                node: Syntax(declaration),\n                macro: \"ProviderDescriptorDefinition\")\n            return []\n        }\n\n        if ProviderMacroIntrospection.hasStaticDescriptor(in: decl) {\n            ProviderMacroError.duplicateDescriptor(context, node: Syntax(declaration), typeName: typeName)\n            return []\n        }\n\n        guard ProviderMacroIntrospection.hasMakeDescriptor(in: decl) else {\n            ProviderMacroError.missingMakeDescriptor(context, node: Syntax(declaration), typeName: typeName)\n            return []\n        }\n\n        return [DeclSyntax(\"public static let descriptor: ProviderDescriptor = Self.makeDescriptor()\")]\n    }\n}\n\npublic struct ProviderImplementationRegistrationMacro: PeerMacro {\n    public static func expansion(\n        of _: AttributeSyntax,\n        providingPeersOf declaration: some DeclSyntaxProtocol,\n        in context: some MacroExpansionContext) throws -> [DeclSyntax]\n    {\n        guard let (decl, typeName) = ProviderMacroIntrospection.typeDecl(from: declaration) else {\n            ProviderMacroError.unsupportedTarget(\n                context,\n                node: Syntax(declaration),\n                macro: \"ProviderImplementationRegistration\")\n            return []\n        }\n\n        guard ProviderMacroIntrospection.hasAccessibleInit(in: decl) else {\n            ProviderMacroError.missingInit(context, node: Syntax(declaration), typeName: typeName)\n            return []\n        }\n\n        let registerName = \"_CodexBarImplementationRegistration_\\(typeName)\"\n        return [\n            DeclSyntax(\n                \"private let \\(raw: registerName) = ProviderImplementationRegistry.register(\\(raw: typeName)())\"),\n        ]\n    }\n}\n\n@main\nstruct CodexBarMacroPlugin: CompilerPlugin {\n    let providingMacros: [Macro.Type] = [\n        ProviderDescriptorRegistrationMacro.self,\n        ProviderDescriptorDefinitionMacro.self,\n        ProviderImplementationRegistrationMacro.self,\n    ]\n}\n"
  },
  {
    "path": "Sources/CodexBarWidget/CodexBarWidgetBundle.swift",
    "content": "import SwiftUI\nimport WidgetKit\n\n@main\nstruct CodexBarWidgetBundle: WidgetBundle {\n    var body: some Widget {\n        CodexBarSwitcherWidget()\n        CodexBarUsageWidget()\n        CodexBarHistoryWidget()\n        CodexBarCompactWidget()\n    }\n}\n\nstruct CodexBarSwitcherWidget: Widget {\n    private let kind = \"CodexBarSwitcherWidget\"\n\n    var body: some WidgetConfiguration {\n        StaticConfiguration(\n            kind: self.kind,\n            provider: CodexBarSwitcherTimelineProvider())\n        { entry in\n            CodexBarSwitcherWidgetView(entry: entry)\n        }\n        .configurationDisplayName(\"CodexBar Switcher\")\n        .description(\"Usage widget with a provider switcher.\")\n        .supportedFamilies([.systemSmall, .systemMedium, .systemLarge])\n    }\n}\n\nstruct CodexBarUsageWidget: Widget {\n    private let kind = \"CodexBarUsageWidget\"\n\n    var body: some WidgetConfiguration {\n        AppIntentConfiguration(\n            kind: self.kind,\n            intent: ProviderSelectionIntent.self,\n            provider: CodexBarTimelineProvider())\n        { entry in\n            CodexBarUsageWidgetView(entry: entry)\n        }\n        .configurationDisplayName(\"CodexBar Usage\")\n        .description(\"Session and weekly usage with credits and costs.\")\n        .supportedFamilies([.systemSmall, .systemMedium, .systemLarge])\n    }\n}\n\nstruct CodexBarHistoryWidget: Widget {\n    private let kind = \"CodexBarHistoryWidget\"\n\n    var body: some WidgetConfiguration {\n        AppIntentConfiguration(\n            kind: self.kind,\n            intent: ProviderSelectionIntent.self,\n            provider: CodexBarTimelineProvider())\n        { entry in\n            CodexBarHistoryWidgetView(entry: entry)\n        }\n        .configurationDisplayName(\"CodexBar History\")\n        .description(\"Usage history chart with recent totals.\")\n        .supportedFamilies([.systemMedium, .systemLarge])\n    }\n}\n\nstruct CodexBarCompactWidget: Widget {\n    private let kind = \"CodexBarCompactWidget\"\n\n    var body: some WidgetConfiguration {\n        AppIntentConfiguration(\n            kind: self.kind,\n            intent: CompactMetricSelectionIntent.self,\n            provider: CodexBarCompactTimelineProvider())\n        { entry in\n            CodexBarCompactWidgetView(entry: entry)\n        }\n        .configurationDisplayName(\"CodexBar Metric\")\n        .description(\"Compact widget for credits or cost.\")\n        .supportedFamilies([.systemSmall])\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarWidget/CodexBarWidgetProvider.swift",
    "content": "import AppIntents\nimport CodexBarCore\nimport SwiftUI\nimport WidgetKit\n\nenum ProviderChoice: String, AppEnum {\n    case codex\n    case claude\n    case gemini\n    case alibaba\n    case antigravity\n    case zai\n    case copilot\n    case minimax\n    case kilo\n    case opencode\n\n    static let typeDisplayRepresentation = TypeDisplayRepresentation(name: \"Provider\")\n\n    static let caseDisplayRepresentations: [ProviderChoice: DisplayRepresentation] = [\n        .codex: DisplayRepresentation(title: \"Codex\"),\n        .claude: DisplayRepresentation(title: \"Claude\"),\n        .gemini: DisplayRepresentation(title: \"Gemini\"),\n        .alibaba: DisplayRepresentation(title: \"Alibaba\"),\n        .antigravity: DisplayRepresentation(title: \"Antigravity\"),\n        .zai: DisplayRepresentation(title: \"z.ai\"),\n        .copilot: DisplayRepresentation(title: \"Copilot\"),\n        .minimax: DisplayRepresentation(title: \"MiniMax\"),\n        .kilo: DisplayRepresentation(title: \"Kilo\"),\n        .opencode: DisplayRepresentation(title: \"OpenCode\"),\n    ]\n\n    var provider: UsageProvider {\n        switch self {\n        case .codex: .codex\n        case .claude: .claude\n        case .gemini: .gemini\n        case .alibaba: .alibaba\n        case .antigravity: .antigravity\n        case .zai: .zai\n        case .copilot: .copilot\n        case .minimax: .minimax\n        case .kilo: .kilo\n        case .opencode: .opencode\n        }\n    }\n\n    // swiftlint:disable:next cyclomatic_complexity\n    init?(provider: UsageProvider) {\n        switch provider {\n        case .codex: self = .codex\n        case .claude: self = .claude\n        case .gemini: self = .gemini\n        case .alibaba: self = .alibaba\n        case .antigravity: self = .antigravity\n        case .cursor: return nil // Cursor not yet supported in widgets\n        case .opencode: self = .opencode\n        case .zai: self = .zai\n        case .factory: return nil // Factory not yet supported in widgets\n        case .copilot: self = .copilot\n        case .minimax: self = .minimax\n        case .vertexai: return nil // Vertex AI not yet supported in widgets\n        case .kilo: self = .kilo\n        case .kiro: return nil // Kiro not yet supported in widgets\n        case .augment: return nil // Augment not yet supported in widgets\n        case .jetbrains: return nil // JetBrains not yet supported in widgets\n        case .kimi: return nil // Kimi not yet supported in widgets\n        case .kimik2: return nil // Kimi K2 not yet supported in widgets\n        case .amp: return nil // Amp not yet supported in widgets\n        case .ollama: return nil // Ollama not yet supported in widgets\n        case .synthetic: return nil // Synthetic not yet supported in widgets\n        case .openrouter: return nil // OpenRouter not yet supported in widgets\n        case .warp: return nil // Warp not yet supported in widgets\n        }\n    }\n}\n\nenum CompactMetric: String, AppEnum {\n    case credits\n    case todayCost\n    case last30DaysCost\n\n    static let typeDisplayRepresentation = TypeDisplayRepresentation(name: \"Metric\")\n\n    static let caseDisplayRepresentations: [CompactMetric: DisplayRepresentation] = [\n        .credits: DisplayRepresentation(title: \"Credits left\"),\n        .todayCost: DisplayRepresentation(title: \"Today cost\"),\n        .last30DaysCost: DisplayRepresentation(title: \"30d cost\"),\n    ]\n}\n\nstruct ProviderSelectionIntent: AppIntent, WidgetConfigurationIntent {\n    static let title: LocalizedStringResource = \"Provider\"\n    static let description = IntentDescription(\"Select the provider to display in the widget.\")\n\n    @Parameter(title: \"Provider\")\n    var provider: ProviderChoice\n\n    init() {\n        self.provider = .codex\n    }\n}\n\nstruct SwitchWidgetProviderIntent: AppIntent {\n    static let title: LocalizedStringResource = \"Switch Provider\"\n    static let description = IntentDescription(\"Switch the provider shown in the widget.\")\n\n    @Parameter(title: \"Provider\")\n    var provider: ProviderChoice\n\n    init() {}\n\n    init(provider: ProviderChoice) {\n        self.provider = provider\n    }\n\n    func perform() async throws -> some IntentResult {\n        WidgetSelectionStore.saveSelectedProvider(self.provider.provider)\n        WidgetCenter.shared.reloadAllTimelines()\n        return .result()\n    }\n}\n\nstruct CompactMetricSelectionIntent: AppIntent, WidgetConfigurationIntent {\n    static let title: LocalizedStringResource = \"Provider + Metric\"\n    static let description = IntentDescription(\"Select the provider and metric to display.\")\n\n    @Parameter(title: \"Provider\")\n    var provider: ProviderChoice\n\n    @Parameter(title: \"Metric\")\n    var metric: CompactMetric\n\n    init() {\n        self.provider = .codex\n        self.metric = .credits\n    }\n}\n\nstruct CodexBarWidgetEntry: TimelineEntry {\n    let date: Date\n    let provider: UsageProvider\n    let snapshot: WidgetSnapshot\n}\n\nstruct CodexBarCompactEntry: TimelineEntry {\n    let date: Date\n    let provider: UsageProvider\n    let metric: CompactMetric\n    let snapshot: WidgetSnapshot\n}\n\nstruct CodexBarSwitcherEntry: TimelineEntry {\n    let date: Date\n    let provider: UsageProvider\n    let availableProviders: [UsageProvider]\n    let snapshot: WidgetSnapshot\n}\n\nstruct CodexBarTimelineProvider: AppIntentTimelineProvider {\n    func placeholder(in context: Context) -> CodexBarWidgetEntry {\n        CodexBarWidgetEntry(\n            date: Date(),\n            provider: .codex,\n            snapshot: WidgetPreviewData.snapshot())\n    }\n\n    func snapshot(for configuration: ProviderSelectionIntent, in context: Context) async -> CodexBarWidgetEntry {\n        let provider = configuration.provider.provider\n        return CodexBarWidgetEntry(\n            date: Date(),\n            provider: provider,\n            snapshot: WidgetSnapshotStore.load() ?? WidgetPreviewData.snapshot())\n    }\n\n    func timeline(\n        for configuration: ProviderSelectionIntent,\n        in context: Context) async -> Timeline<CodexBarWidgetEntry>\n    {\n        let provider = configuration.provider.provider\n        let snapshot = WidgetSnapshotStore.load() ?? WidgetPreviewData.snapshot()\n        let entry = CodexBarWidgetEntry(date: Date(), provider: provider, snapshot: snapshot)\n        let refresh = Date().addingTimeInterval(30 * 60)\n        return Timeline(entries: [entry], policy: .after(refresh))\n    }\n}\n\nstruct CodexBarSwitcherTimelineProvider: TimelineProvider {\n    func placeholder(in context: Context) -> CodexBarSwitcherEntry {\n        let snapshot = WidgetPreviewData.snapshot()\n        let providers = self.availableProviders(from: snapshot)\n        return CodexBarSwitcherEntry(\n            date: Date(),\n            provider: providers.first ?? .codex,\n            availableProviders: providers,\n            snapshot: snapshot)\n    }\n\n    func getSnapshot(in context: Context, completion: @escaping (CodexBarSwitcherEntry) -> Void) {\n        completion(self.makeEntry())\n    }\n\n    func getTimeline(in context: Context, completion: @escaping (Timeline<CodexBarSwitcherEntry>) -> Void) {\n        let entry = self.makeEntry()\n        let refresh = Date().addingTimeInterval(30 * 60)\n        completion(Timeline(entries: [entry], policy: .after(refresh)))\n    }\n\n    private func makeEntry() -> CodexBarSwitcherEntry {\n        let snapshot = WidgetSnapshotStore.load() ?? WidgetPreviewData.snapshot()\n        let providers = self.availableProviders(from: snapshot)\n        let stored = WidgetSelectionStore.loadSelectedProvider()\n        let selected = providers.first { $0 == stored } ?? providers.first ?? .codex\n        if selected != stored {\n            WidgetSelectionStore.saveSelectedProvider(selected)\n        }\n        return CodexBarSwitcherEntry(\n            date: Date(),\n            provider: selected,\n            availableProviders: providers,\n            snapshot: snapshot)\n    }\n\n    private func availableProviders(from snapshot: WidgetSnapshot) -> [UsageProvider] {\n        Self.supportedProviders(from: snapshot)\n    }\n\n    static func supportedProviders(from snapshot: WidgetSnapshot) -> [UsageProvider] {\n        let enabled = snapshot.enabledProviders\n        let providers = enabled.isEmpty ? snapshot.entries.map(\\.provider) : enabled\n        let supported = providers.filter { ProviderChoice(provider: $0) != nil }\n        return supported.isEmpty ? [.codex] : supported\n    }\n}\n\nstruct CodexBarCompactTimelineProvider: AppIntentTimelineProvider {\n    func placeholder(in context: Context) -> CodexBarCompactEntry {\n        CodexBarCompactEntry(\n            date: Date(),\n            provider: .codex,\n            metric: .credits,\n            snapshot: WidgetPreviewData.snapshot())\n    }\n\n    func snapshot(for configuration: CompactMetricSelectionIntent, in context: Context) async -> CodexBarCompactEntry {\n        let provider = configuration.provider.provider\n        return CodexBarCompactEntry(\n            date: Date(),\n            provider: provider,\n            metric: configuration.metric,\n            snapshot: WidgetSnapshotStore.load() ?? WidgetPreviewData.snapshot())\n    }\n\n    func timeline(\n        for configuration: CompactMetricSelectionIntent,\n        in context: Context) async -> Timeline<CodexBarCompactEntry>\n    {\n        let provider = configuration.provider.provider\n        let snapshot = WidgetSnapshotStore.load() ?? WidgetPreviewData.snapshot()\n        let entry = CodexBarCompactEntry(\n            date: Date(),\n            provider: provider,\n            metric: configuration.metric,\n            snapshot: snapshot)\n        let refresh = Date().addingTimeInterval(30 * 60)\n        return Timeline(entries: [entry], policy: .after(refresh))\n    }\n}\n\nenum WidgetPreviewData {\n    static func snapshot() -> WidgetSnapshot {\n        let primary = RateWindow(usedPercent: 35, windowMinutes: nil, resetsAt: nil, resetDescription: \"Resets in 4h\")\n        let secondary = RateWindow(usedPercent: 60, windowMinutes: nil, resetsAt: nil, resetDescription: \"Resets in 3d\")\n        let entry = WidgetSnapshot.ProviderEntry(\n            provider: .codex,\n            updatedAt: Date(),\n            primary: primary,\n            secondary: secondary,\n            tertiary: nil,\n            creditsRemaining: 1243.4,\n            codeReviewRemainingPercent: 78,\n            tokenUsage: WidgetSnapshot.TokenUsageSummary(\n                sessionCostUSD: 12.4,\n                sessionTokens: 420_000,\n                last30DaysCostUSD: 923.8,\n                last30DaysTokens: 12_400_000),\n            dailyUsage: [\n                WidgetSnapshot.DailyUsagePoint(dayKey: \"2025-12-01\", totalTokens: 120_000, costUSD: 15.2),\n                WidgetSnapshot.DailyUsagePoint(dayKey: \"2025-12-02\", totalTokens: 80000, costUSD: 10.1),\n                WidgetSnapshot.DailyUsagePoint(dayKey: \"2025-12-03\", totalTokens: 140_000, costUSD: 17.9),\n                WidgetSnapshot.DailyUsagePoint(dayKey: \"2025-12-04\", totalTokens: 90000, costUSD: 11.4),\n                WidgetSnapshot.DailyUsagePoint(dayKey: \"2025-12-05\", totalTokens: 160_000, costUSD: 19.8),\n                WidgetSnapshot.DailyUsagePoint(dayKey: \"2025-12-06\", totalTokens: 70000, costUSD: 8.9),\n                WidgetSnapshot.DailyUsagePoint(dayKey: \"2025-12-07\", totalTokens: 110_000, costUSD: 13.7),\n            ])\n        return WidgetSnapshot(entries: [entry], generatedAt: Date())\n    }\n}\n"
  },
  {
    "path": "Sources/CodexBarWidget/CodexBarWidgetViews.swift",
    "content": "import CodexBarCore\nimport SwiftUI\nimport WidgetKit\n\nstruct CodexBarUsageWidgetView: View {\n    @Environment(\\.widgetFamily) private var family\n    let entry: CodexBarWidgetEntry\n\n    var body: some View {\n        let providerEntry = self.entry.snapshot.entries.first { $0.provider == self.entry.provider }\n        ZStack {\n            Color.black.opacity(0.02)\n            if let providerEntry {\n                self.content(providerEntry: providerEntry)\n            } else {\n                self.emptyState\n            }\n        }\n        .containerBackground(.fill.tertiary, for: .widget)\n    }\n\n    @ViewBuilder\n    private func content(providerEntry: WidgetSnapshot.ProviderEntry) -> some View {\n        switch self.family {\n        case .systemSmall:\n            SmallUsageView(entry: providerEntry)\n        case .systemMedium:\n            MediumUsageView(entry: providerEntry)\n        default:\n            LargeUsageView(entry: providerEntry)\n        }\n    }\n\n    private var emptyState: some View {\n        VStack(alignment: .leading, spacing: 6) {\n            Text(\"Open CodexBar\")\n                .font(.body)\n                .fontWeight(.semibold)\n            Text(\"Usage data will appear once the app refreshes.\")\n                .font(.caption)\n                .foregroundStyle(.secondary)\n        }\n        .padding(12)\n    }\n}\n\nstruct CodexBarHistoryWidgetView: View {\n    @Environment(\\.widgetFamily) private var family\n    let entry: CodexBarWidgetEntry\n\n    var body: some View {\n        let providerEntry = self.entry.snapshot.entries.first { $0.provider == self.entry.provider }\n        ZStack {\n            Color.black.opacity(0.02)\n            if let providerEntry {\n                HistoryView(entry: providerEntry, isLarge: self.family == .systemLarge)\n            } else {\n                self.emptyState\n            }\n        }\n        .containerBackground(.fill.tertiary, for: .widget)\n    }\n\n    private var emptyState: some View {\n        VStack(alignment: .leading, spacing: 6) {\n            Text(\"Open CodexBar\")\n                .font(.body)\n                .fontWeight(.semibold)\n            Text(\"Usage history will appear after a refresh.\")\n                .font(.caption)\n                .foregroundStyle(.secondary)\n        }\n        .padding(12)\n    }\n}\n\nstruct CodexBarCompactWidgetView: View {\n    let entry: CodexBarCompactEntry\n\n    var body: some View {\n        let providerEntry = self.entry.snapshot.entries.first { $0.provider == self.entry.provider }\n        ZStack {\n            Color.black.opacity(0.02)\n            if let providerEntry {\n                CompactMetricView(entry: providerEntry, metric: self.entry.metric)\n            } else {\n                self.emptyState\n            }\n        }\n        .containerBackground(.fill.tertiary, for: .widget)\n    }\n\n    private var emptyState: some View {\n        VStack(alignment: .leading, spacing: 6) {\n            Text(\"Open CodexBar\")\n                .font(.body)\n                .fontWeight(.semibold)\n            Text(\"Usage data will appear once the app refreshes.\")\n                .font(.caption)\n                .foregroundStyle(.secondary)\n        }\n        .padding(12)\n    }\n}\n\nstruct CodexBarSwitcherWidgetView: View {\n    @Environment(\\.widgetFamily) private var family\n    let entry: CodexBarSwitcherEntry\n\n    var body: some View {\n        let providerEntry = self.entry.snapshot.entries.first { $0.provider == self.entry.provider }\n        ZStack {\n            Color.black.opacity(0.02)\n            VStack(alignment: .leading, spacing: 10) {\n                ProviderSwitcherRow(\n                    providers: self.entry.availableProviders,\n                    selected: self.entry.provider,\n                    updatedAt: providerEntry?.updatedAt ?? Date(),\n                    compact: self.family == .systemSmall,\n                    showsTimestamp: self.family != .systemSmall)\n                if let providerEntry {\n                    self.content(providerEntry: providerEntry)\n                } else {\n                    self.emptyState\n                }\n            }\n            .padding(12)\n        }\n        .containerBackground(.fill.tertiary, for: .widget)\n    }\n\n    @ViewBuilder\n    private func content(providerEntry: WidgetSnapshot.ProviderEntry) -> some View {\n        switch self.family {\n        case .systemSmall:\n            SwitcherSmallUsageView(entry: providerEntry)\n        case .systemMedium:\n            SwitcherMediumUsageView(entry: providerEntry)\n        default:\n            SwitcherLargeUsageView(entry: providerEntry)\n        }\n    }\n\n    private var emptyState: some View {\n        VStack(alignment: .leading, spacing: 6) {\n            Text(\"Open CodexBar\")\n                .font(.caption)\n                .foregroundStyle(.secondary)\n            Text(\"Usage data appears after a refresh.\")\n                .font(.caption2)\n                .foregroundStyle(.secondary)\n        }\n    }\n}\n\nprivate struct CompactMetricView: View {\n    let entry: WidgetSnapshot.ProviderEntry\n    let metric: CompactMetric\n\n    var body: some View {\n        let display = self.display\n        VStack(alignment: .leading, spacing: 8) {\n            HeaderView(provider: self.entry.provider, updatedAt: self.entry.updatedAt)\n            VStack(alignment: .leading, spacing: 2) {\n                Text(display.value)\n                    .font(.title2)\n                    .fontWeight(.semibold)\n                Text(display.label)\n                    .font(.caption2)\n                    .foregroundStyle(.secondary)\n                if let detail = display.detail {\n                    Text(detail)\n                        .font(.caption2)\n                        .foregroundStyle(.secondary)\n                }\n            }\n        }\n        .padding(12)\n    }\n\n    private var display: (value: String, label: String, detail: String?) {\n        switch self.metric {\n        case .credits:\n            let value = self.entry.creditsRemaining.map(WidgetFormat.credits) ?? \"—\"\n            return (value, \"Credits left\", nil)\n        case .todayCost:\n            let value = self.entry.tokenUsage?.sessionCostUSD.map(WidgetFormat.usd) ?? \"—\"\n            let detail = self.entry.tokenUsage?.sessionTokens.map(WidgetFormat.tokenCount)\n            return (value, \"Today cost\", detail)\n        case .last30DaysCost:\n            let value = self.entry.tokenUsage?.last30DaysCostUSD.map(WidgetFormat.usd) ?? \"—\"\n            let detail = self.entry.tokenUsage?.last30DaysTokens.map(WidgetFormat.tokenCount)\n            return (value, \"30d cost\", detail)\n        }\n    }\n}\n\nprivate struct ProviderSwitcherRow: View {\n    let providers: [UsageProvider]\n    let selected: UsageProvider\n    let updatedAt: Date\n    let compact: Bool\n    let showsTimestamp: Bool\n\n    var body: some View {\n        HStack(spacing: self.compact ? 4 : 6) {\n            ForEach(self.providers, id: \\.self) { provider in\n                ProviderSwitchChip(\n                    provider: provider,\n                    selected: provider == self.selected,\n                    compact: self.compact)\n            }\n            if self.showsTimestamp {\n                Spacer(minLength: 6)\n                Text(WidgetFormat.relativeDate(self.updatedAt))\n                    .font(.caption2)\n                    .foregroundStyle(.secondary)\n            }\n        }\n    }\n}\n\nprivate struct ProviderSwitchChip: View {\n    let provider: UsageProvider\n    let selected: Bool\n    let compact: Bool\n\n    var body: some View {\n        let label = self.compact ? self.shortLabel : self.longLabel\n        let background = self.selected\n            ? WidgetColors.color(for: self.provider).opacity(0.2)\n            : Color.primary.opacity(0.08)\n\n        if let choice = ProviderChoice(provider: self.provider) {\n            Button(intent: SwitchWidgetProviderIntent(provider: choice)) {\n                Text(label)\n                    .font(self.compact ? .caption2.weight(.semibold) : .caption.weight(.semibold))\n                    .foregroundStyle(self.selected ? Color.primary : Color.secondary)\n                    .padding(.horizontal, self.compact ? 6 : 8)\n                    .padding(.vertical, self.compact ? 3 : 4)\n                    .background(Capsule().fill(background))\n            }\n            .buttonStyle(.plain)\n        } else {\n            Text(label)\n                .font(self.compact ? .caption2.weight(.semibold) : .caption.weight(.semibold))\n                .foregroundStyle(self.selected ? Color.primary : Color.secondary)\n                .padding(.horizontal, self.compact ? 6 : 8)\n                .padding(.vertical, self.compact ? 3 : 4)\n                .background(Capsule().fill(background))\n        }\n    }\n\n    private var longLabel: String {\n        ProviderDefaults.metadata[self.provider]?.displayName ?? self.provider.rawValue.capitalized\n    }\n\n    private var shortLabel: String {\n        switch self.provider {\n        case .codex: \"Codex\"\n        case .claude: \"Claude\"\n        case .gemini: \"Gemini\"\n        case .antigravity: \"Anti\"\n        case .cursor: \"Cursor\"\n        case .opencode: \"OpenCode\"\n        case .alibaba: \"Alibaba\"\n        case .zai: \"z.ai\"\n        case .factory: \"Droid\"\n        case .copilot: \"Copilot\"\n        case .minimax: \"MiniMax\"\n        case .vertexai: \"Vertex\"\n        case .kilo: \"Kilo\"\n        case .kiro: \"Kiro\"\n        case .augment: \"Augment\"\n        case .jetbrains: \"JetBrains\"\n        case .kimi: \"Kimi\"\n        case .kimik2: \"Kimi K2\"\n        case .amp: \"Amp\"\n        case .ollama: \"Ollama\"\n        case .synthetic: \"Synthetic\"\n        case .openrouter: \"OpenRouter\"\n        case .warp: \"Warp\"\n        }\n    }\n}\n\nprivate struct SwitcherSmallUsageView: View {\n    let entry: WidgetSnapshot.ProviderEntry\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 8) {\n            UsageBarRow(\n                title: ProviderDefaults.metadata[self.entry.provider]?.sessionLabel ?? \"Session\",\n                percentLeft: self.entry.primary?.remainingPercent,\n                color: WidgetColors.color(for: self.entry.provider))\n            UsageBarRow(\n                title: ProviderDefaults.metadata[self.entry.provider]?.weeklyLabel ?? \"Weekly\",\n                percentLeft: self.entry.secondary?.remainingPercent,\n                color: WidgetColors.color(for: self.entry.provider))\n            if let codeReview = entry.codeReviewRemainingPercent {\n                UsageBarRow(\n                    title: \"Code review\",\n                    percentLeft: codeReview,\n                    color: WidgetColors.color(for: self.entry.provider))\n            }\n        }\n    }\n}\n\nprivate struct SwitcherMediumUsageView: View {\n    let entry: WidgetSnapshot.ProviderEntry\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 10) {\n            UsageBarRow(\n                title: ProviderDefaults.metadata[self.entry.provider]?.sessionLabel ?? \"Session\",\n                percentLeft: self.entry.primary?.remainingPercent,\n                color: WidgetColors.color(for: self.entry.provider))\n            UsageBarRow(\n                title: ProviderDefaults.metadata[self.entry.provider]?.weeklyLabel ?? \"Weekly\",\n                percentLeft: self.entry.secondary?.remainingPercent,\n                color: WidgetColors.color(for: self.entry.provider))\n            if let credits = entry.creditsRemaining {\n                ValueLine(title: \"Credits\", value: WidgetFormat.credits(credits))\n            }\n            if let token = entry.tokenUsage {\n                ValueLine(\n                    title: \"Today\",\n                    value: WidgetFormat.costAndTokens(cost: token.sessionCostUSD, tokens: token.sessionTokens))\n            }\n        }\n    }\n}\n\nprivate struct SwitcherLargeUsageView: View {\n    let entry: WidgetSnapshot.ProviderEntry\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 12) {\n            UsageBarRow(\n                title: ProviderDefaults.metadata[self.entry.provider]?.sessionLabel ?? \"Session\",\n                percentLeft: self.entry.primary?.remainingPercent,\n                color: WidgetColors.color(for: self.entry.provider))\n            UsageBarRow(\n                title: ProviderDefaults.metadata[self.entry.provider]?.weeklyLabel ?? \"Weekly\",\n                percentLeft: self.entry.secondary?.remainingPercent,\n                color: WidgetColors.color(for: self.entry.provider))\n            if let codeReview = entry.codeReviewRemainingPercent {\n                UsageBarRow(\n                    title: \"Code review\",\n                    percentLeft: codeReview,\n                    color: WidgetColors.color(for: self.entry.provider))\n            }\n            if let credits = entry.creditsRemaining {\n                ValueLine(title: \"Credits\", value: WidgetFormat.credits(credits))\n            }\n            if let token = entry.tokenUsage {\n                VStack(alignment: .leading, spacing: 4) {\n                    ValueLine(\n                        title: \"Today\",\n                        value: WidgetFormat.costAndTokens(cost: token.sessionCostUSD, tokens: token.sessionTokens))\n                    ValueLine(\n                        title: \"30d\",\n                        value: WidgetFormat.costAndTokens(\n                            cost: token.last30DaysCostUSD,\n                            tokens: token.last30DaysTokens))\n                }\n            }\n            UsageHistoryChart(points: self.entry.dailyUsage, color: WidgetColors.color(for: self.entry.provider))\n                .frame(height: 50)\n        }\n    }\n}\n\nprivate struct SmallUsageView: View {\n    let entry: WidgetSnapshot.ProviderEntry\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 8) {\n            HeaderView(provider: self.entry.provider, updatedAt: self.entry.updatedAt)\n            UsageBarRow(\n                title: ProviderDefaults.metadata[self.entry.provider]?.sessionLabel ?? \"Session\",\n                percentLeft: self.entry.primary?.remainingPercent,\n                color: WidgetColors.color(for: self.entry.provider))\n            UsageBarRow(\n                title: ProviderDefaults.metadata[self.entry.provider]?.weeklyLabel ?? \"Weekly\",\n                percentLeft: self.entry.secondary?.remainingPercent,\n                color: WidgetColors.color(for: self.entry.provider))\n            if let codeReview = entry.codeReviewRemainingPercent {\n                UsageBarRow(\n                    title: \"Code review\",\n                    percentLeft: codeReview,\n                    color: WidgetColors.color(for: self.entry.provider))\n            }\n        }\n        .padding(12)\n    }\n}\n\nprivate struct MediumUsageView: View {\n    let entry: WidgetSnapshot.ProviderEntry\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 10) {\n            HeaderView(provider: self.entry.provider, updatedAt: self.entry.updatedAt)\n            UsageBarRow(\n                title: ProviderDefaults.metadata[self.entry.provider]?.sessionLabel ?? \"Session\",\n                percentLeft: self.entry.primary?.remainingPercent,\n                color: WidgetColors.color(for: self.entry.provider))\n            UsageBarRow(\n                title: ProviderDefaults.metadata[self.entry.provider]?.weeklyLabel ?? \"Weekly\",\n                percentLeft: self.entry.secondary?.remainingPercent,\n                color: WidgetColors.color(for: self.entry.provider))\n            if let credits = entry.creditsRemaining {\n                ValueLine(title: \"Credits\", value: WidgetFormat.credits(credits))\n            }\n            if let token = entry.tokenUsage {\n                ValueLine(\n                    title: \"Today\",\n                    value: WidgetFormat.costAndTokens(cost: token.sessionCostUSD, tokens: token.sessionTokens))\n            }\n        }\n        .padding(12)\n    }\n}\n\nprivate struct LargeUsageView: View {\n    let entry: WidgetSnapshot.ProviderEntry\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 12) {\n            HeaderView(provider: self.entry.provider, updatedAt: self.entry.updatedAt)\n            UsageBarRow(\n                title: ProviderDefaults.metadata[self.entry.provider]?.sessionLabel ?? \"Session\",\n                percentLeft: self.entry.primary?.remainingPercent,\n                color: WidgetColors.color(for: self.entry.provider))\n            UsageBarRow(\n                title: ProviderDefaults.metadata[self.entry.provider]?.weeklyLabel ?? \"Weekly\",\n                percentLeft: self.entry.secondary?.remainingPercent,\n                color: WidgetColors.color(for: self.entry.provider))\n            if let codeReview = entry.codeReviewRemainingPercent {\n                UsageBarRow(\n                    title: \"Code review\",\n                    percentLeft: codeReview,\n                    color: WidgetColors.color(for: self.entry.provider))\n            }\n            if let credits = entry.creditsRemaining {\n                ValueLine(title: \"Credits\", value: WidgetFormat.credits(credits))\n            }\n            if let token = entry.tokenUsage {\n                VStack(alignment: .leading, spacing: 4) {\n                    ValueLine(\n                        title: \"Today\",\n                        value: WidgetFormat.costAndTokens(cost: token.sessionCostUSD, tokens: token.sessionTokens))\n                    ValueLine(\n                        title: \"30d\",\n                        value: WidgetFormat.costAndTokens(\n                            cost: token.last30DaysCostUSD,\n                            tokens: token.last30DaysTokens))\n                }\n            }\n            UsageHistoryChart(points: self.entry.dailyUsage, color: WidgetColors.color(for: self.entry.provider))\n                .frame(height: 50)\n        }\n        .padding(12)\n    }\n}\n\nprivate struct HistoryView: View {\n    let entry: WidgetSnapshot.ProviderEntry\n    let isLarge: Bool\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 12) {\n            HeaderView(provider: self.entry.provider, updatedAt: self.entry.updatedAt)\n            UsageHistoryChart(points: self.entry.dailyUsage, color: WidgetColors.color(for: self.entry.provider))\n                .frame(height: self.isLarge ? 90 : 60)\n            if let token = entry.tokenUsage {\n                ValueLine(\n                    title: \"Today\",\n                    value: WidgetFormat.costAndTokens(cost: token.sessionCostUSD, tokens: token.sessionTokens))\n                ValueLine(\n                    title: \"30d\",\n                    value: WidgetFormat.costAndTokens(cost: token.last30DaysCostUSD, tokens: token.last30DaysTokens))\n            }\n        }\n        .padding(12)\n    }\n}\n\nprivate struct HeaderView: View {\n    let provider: UsageProvider\n    let updatedAt: Date\n\n    var body: some View {\n        HStack(alignment: .firstTextBaseline) {\n            Text(ProviderDefaults.metadata[self.provider]?.displayName ?? self.provider.rawValue.capitalized)\n                .font(.body)\n                .fontWeight(.semibold)\n            Spacer()\n            Text(WidgetFormat.relativeDate(self.updatedAt))\n                .font(.caption2)\n                .foregroundStyle(.secondary)\n        }\n    }\n}\n\nprivate struct UsageBarRow: View {\n    let title: String\n    let percentLeft: Double?\n    let color: Color\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 4) {\n            HStack {\n                Text(self.title)\n                    .font(.caption)\n                Spacer()\n                Text(WidgetFormat.percent(self.percentLeft))\n                    .font(.caption)\n                    .foregroundStyle(.secondary)\n            }\n            GeometryReader { proxy in\n                let width = max(0, min(1, (percentLeft ?? 0) / 100)) * proxy.size.width\n                ZStack(alignment: .leading) {\n                    Capsule().fill(Color.primary.opacity(0.08))\n                    Capsule().fill(self.color).frame(width: width)\n                }\n            }\n            .frame(height: 6)\n        }\n    }\n}\n\nprivate struct ValueLine: View {\n    let title: String\n    let value: String\n\n    var body: some View {\n        HStack(spacing: 6) {\n            Text(self.title)\n                .font(.caption)\n                .foregroundStyle(.secondary)\n            Text(self.value)\n                .font(.caption)\n        }\n    }\n}\n\nprivate struct UsageHistoryChart: View {\n    let points: [WidgetSnapshot.DailyUsagePoint]\n    let color: Color\n\n    var body: some View {\n        let values = self.points.map { point -> Double in\n            if let cost = point.costUSD { return cost }\n            return Double(point.totalTokens ?? 0)\n        }\n        let maxValue = values.max() ?? 0\n        HStack(alignment: .bottom, spacing: 2) {\n            ForEach(values.indices, id: \\.self) { index in\n                let value = values[index]\n                let height = maxValue > 0 ? CGFloat(value / maxValue) : 0\n                RoundedRectangle(cornerRadius: 2)\n                    .fill(self.color.opacity(0.85))\n                    .frame(maxWidth: .infinity)\n                    .scaleEffect(x: 1, y: height, anchor: .bottom)\n                    .animation(.easeOut(duration: 0.2), value: height)\n            }\n        }\n    }\n}\n\nenum WidgetColors {\n    // swiftlint:disable:next cyclomatic_complexity\n    static func color(for provider: UsageProvider) -> Color {\n        switch provider {\n        case .codex:\n            Color(red: 73 / 255, green: 163 / 255, blue: 176 / 255)\n        case .claude:\n            Color(red: 204 / 255, green: 124 / 255, blue: 94 / 255)\n        case .gemini:\n            Color(red: 171 / 255, green: 135 / 255, blue: 234 / 255)\n        case .antigravity:\n            Color(red: 96 / 255, green: 186 / 255, blue: 126 / 255)\n        case .cursor:\n            Color(red: 0 / 255, green: 191 / 255, blue: 165 / 255) // #00BFA5 - Cursor teal\n        case .opencode:\n            Color(red: 59 / 255, green: 130 / 255, blue: 246 / 255)\n        case .alibaba:\n            Color(red: 1.0, green: 106 / 255, blue: 0)\n        case .zai:\n            Color(red: 232 / 255, green: 90 / 255, blue: 106 / 255)\n        case .factory:\n            Color(red: 255 / 255, green: 107 / 255, blue: 53 / 255) // Factory orange\n        case .copilot:\n            Color(red: 168 / 255, green: 85 / 255, blue: 247 / 255) // Purple\n        case .minimax:\n            Color(red: 254 / 255, green: 96 / 255, blue: 60 / 255)\n        case .vertexai:\n            Color(red: 66 / 255, green: 133 / 255, blue: 244 / 255) // Google Blue\n        case .kilo:\n            Color(red: 242 / 255, green: 112 / 255, blue: 39 / 255) // Kilo orange\n        case .kiro:\n            Color(red: 255 / 255, green: 153 / 255, blue: 0 / 255) // AWS orange\n        case .augment:\n            Color(red: 99 / 255, green: 102 / 255, blue: 241 / 255) // Augment purple\n        case .jetbrains:\n            Color(red: 255 / 255, green: 51 / 255, blue: 153 / 255) // JetBrains pink\n        case .kimi:\n            Color(red: 254 / 255, green: 96 / 255, blue: 60 / 255) // Kimi orange\n        case .kimik2:\n            Color(red: 76 / 255, green: 0 / 255, blue: 255 / 255) // Kimi K2 purple\n        case .amp:\n            Color(red: 220 / 255, green: 38 / 255, blue: 38 / 255) // Amp red\n        case .ollama:\n            Color(red: 32 / 255, green: 32 / 255, blue: 32 / 255) // Ollama charcoal\n        case .synthetic:\n            Color(red: 20 / 255, green: 20 / 255, blue: 20 / 255) // Synthetic charcoal\n        case .openrouter:\n            Color(red: 111 / 255, green: 66 / 255, blue: 193 / 255) // OpenRouter purple\n        case .warp:\n            Color(red: 147 / 255, green: 139 / 255, blue: 180 / 255)\n        }\n    }\n}\n\nenum WidgetFormat {\n    static func percent(_ value: Double?) -> String {\n        guard let value else { return \"—\" }\n        return String(format: \"%.0f%%\", value)\n    }\n\n    static func credits(_ value: Double) -> String {\n        let formatter = NumberFormatter()\n        formatter.numberStyle = .decimal\n        formatter.maximumFractionDigits = 2\n        formatter.minimumFractionDigits = 0\n        return formatter.string(from: NSNumber(value: value)) ?? String(format: \"%.2f\", value)\n    }\n\n    static func costAndTokens(cost: Double?, tokens: Int?) -> String {\n        let costText = cost.map(self.usd) ?? \"—\"\n        if let tokens {\n            return \"\\(costText) · \\(self.tokenCount(tokens))\"\n        }\n        return costText\n    }\n\n    static func usd(_ value: Double) -> String {\n        let formatter = NumberFormatter()\n        formatter.numberStyle = .currency\n        formatter.currencyCode = \"USD\"\n        formatter.maximumFractionDigits = 2\n        formatter.minimumFractionDigits = 2\n        return formatter.string(from: NSNumber(value: value)) ?? String(format: \"$%.2f\", value)\n    }\n\n    static func tokenCount(_ value: Int) -> String {\n        let formatter = NumberFormatter()\n        formatter.numberStyle = .decimal\n        formatter.maximumFractionDigits = 0\n        let raw = formatter.string(from: NSNumber(value: value)) ?? \"\\(value)\"\n        return \"\\(raw) tokens\"\n    }\n\n    static func relativeDate(_ date: Date) -> String {\n        let formatter = RelativeDateTimeFormatter()\n        formatter.unitsStyle = .short\n        return formatter.localizedString(for: date, relativeTo: Date())\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/AlibabaCodingPlanCookieImporterTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\n#if os(macOS)\nimport SweetCookieKit\n\nstruct AlibabaCodingPlanCookieImporterTests {\n    @Test\n    func domainMatchingRequiresExactOrLabelBoundedSuffix() {\n        #expect(AlibabaCodingPlanCookieImporter.matchesCookieDomain(\"console.aliyun.com\"))\n        #expect(AlibabaCodingPlanCookieImporter.matchesCookieDomain(\".modelstudio.console.alibabacloud.com\"))\n        #expect(AlibabaCodingPlanCookieImporter.matchesCookieDomain(\"foo.aliyun.com\"))\n        #expect(AlibabaCodingPlanCookieImporter.matchesCookieDomain(\"evilaliyun.com\") == false)\n        #expect(AlibabaCodingPlanCookieImporter.matchesCookieDomain(\"notalibabacloud.com\") == false)\n    }\n\n    @Test\n    func cookieImportCandidatesHonorProvidedBrowserOrder() throws {\n        BrowserCookieAccessGate.resetForTesting()\n\n        let temp = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)\n        try FileManager.default.createDirectory(at: temp, withIntermediateDirectories: true)\n        defer { try? FileManager.default.removeItem(at: temp) }\n\n        let firefoxProfile = temp\n            .appendingPathComponent(\"Library\")\n            .appendingPathComponent(\"Application Support\")\n            .appendingPathComponent(\"Firefox\")\n            .appendingPathComponent(\"Profiles\")\n            .appendingPathComponent(\"abc.default-release\")\n        try FileManager.default.createDirectory(at: firefoxProfile, withIntermediateDirectories: true)\n        FileManager.default.createFile(\n            atPath: firefoxProfile.appendingPathComponent(\"cookies.sqlite\").path,\n            contents: Data())\n\n        let detection = BrowserDetection(homeDirectory: temp.path, cacheTTL: 0)\n        let importOrder: BrowserCookieImportOrder = [.firefox, .safari, .chrome]\n\n        let candidates = AlibabaCodingPlanCookieImporter.cookieImportCandidates(\n            browserDetection: detection,\n            importOrder: importOrder)\n\n        let expected: [Browser] = [.firefox, .safari]\n        #expect(candidates == expected)\n    }\n\n    @Test\n    func defaultCookieImportCandidatesPreferChromeBeforeSafari() throws {\n        BrowserCookieAccessGate.resetForTesting()\n\n        let temp = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)\n        try FileManager.default.createDirectory(at: temp, withIntermediateDirectories: true)\n        defer { try? FileManager.default.removeItem(at: temp) }\n\n        let chromeProfile = temp\n            .appendingPathComponent(\"Library\")\n            .appendingPathComponent(\"Application Support\")\n            .appendingPathComponent(\"Google\")\n            .appendingPathComponent(\"Chrome\")\n            .appendingPathComponent(\"Default\")\n        try FileManager.default.createDirectory(at: chromeProfile, withIntermediateDirectories: true)\n        let cookiesDir = chromeProfile.appendingPathComponent(\"Network\")\n        try FileManager.default.createDirectory(at: cookiesDir, withIntermediateDirectories: true)\n        FileManager.default.createFile(\n            atPath: cookiesDir.appendingPathComponent(\"Cookies\").path,\n            contents: Data())\n\n        let detection = BrowserDetection(homeDirectory: temp.path, cacheTTL: 0)\n        let candidates = AlibabaCodingPlanCookieImporter.cookieImportCandidates(browserDetection: detection)\n\n        #expect(Array(candidates.prefix(2)) == [.chrome, .safari])\n    }\n}\n\n#else\n\nstruct AlibabaCodingPlanCookieImporterTests {\n    @Test\n    func nonMacOSPlaceholder() {\n        #expect(true)\n    }\n}\n\n#endif\n"
  },
  {
    "path": "Tests/CodexBarTests/AlibabaCodingPlanProviderTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\n@Suite\nstruct AlibabaCodingPlanSettingsReaderTests {\n    @Test\n    func apiTokenReadsFromEnvironment() {\n        let token = AlibabaCodingPlanSettingsReader.apiToken(environment: [\"ALIBABA_CODING_PLAN_API_KEY\": \"abc123\"])\n        #expect(token == \"abc123\")\n    }\n\n    @Test\n    func apiTokenStripsQuotes() {\n        let token = AlibabaCodingPlanSettingsReader\n            .apiToken(environment: [\"ALIBABA_CODING_PLAN_API_KEY\": \"\\\"token-xyz\\\"\"])\n        #expect(token == \"token-xyz\")\n    }\n\n    @Test\n    func quotaURLInfersScheme() {\n        let url = AlibabaCodingPlanSettingsReader\n            .quotaURL(environment: [AlibabaCodingPlanSettingsReader\n                    .quotaURLKey: \"modelstudio.console.alibabacloud.com/data/api.json\"])\n        #expect(url?.absoluteString == \"https://modelstudio.console.alibabacloud.com/data/api.json\")\n    }\n\n    @Test\n    func missingCookieErrorIncludesAccessHintWhenPresent() {\n        let error = AlibabaCodingPlanSettingsError\n            .missingCookie(details: \"Safari cookie file exists but is not readable.\")\n        #expect(error.errorDescription?.contains(\"Safari cookie file exists but is not readable.\") == true)\n    }\n}\n\n@Suite\nstruct AlibabaCodingPlanUsageSnapshotTests {\n    @Test\n    func mapsUsageSnapshotWindows() {\n        let now = Date(timeIntervalSince1970: 1_700_000_000)\n        let reset5h = Date(timeIntervalSince1970: 1_700_000_300)\n        let resetWeek = Date(timeIntervalSince1970: 1_700_010_000)\n        let resetMonth = Date(timeIntervalSince1970: 1_700_100_000)\n        let snapshot = AlibabaCodingPlanUsageSnapshot(\n            planName: \"Pro\",\n            fiveHourUsedQuota: 20,\n            fiveHourTotalQuota: 100,\n            fiveHourNextRefreshTime: reset5h,\n            weeklyUsedQuota: 120,\n            weeklyTotalQuota: 400,\n            weeklyNextRefreshTime: resetWeek,\n            monthlyUsedQuota: 500,\n            monthlyTotalQuota: 2000,\n            monthlyNextRefreshTime: resetMonth,\n            updatedAt: now)\n\n        let usage = snapshot.toUsageSnapshot()\n\n        #expect(usage.primary?.usedPercent == 20)\n        #expect(usage.primary?.windowMinutes == 300)\n        #expect(usage.secondary?.usedPercent == 30)\n        #expect(usage.secondary?.windowMinutes == 10080)\n        #expect(usage.tertiary?.usedPercent == 25)\n        #expect(usage.tertiary?.windowMinutes == 43200)\n        #expect(usage.loginMethod(for: .alibaba) == \"Pro\")\n    }\n\n    @Test\n    func shiftsPrimaryResetForwardWhenBackendResetIsNotFuture() {\n        let now = Date(timeIntervalSince1970: 1_700_000_000)\n        let stalePrimaryReset = Date(timeIntervalSince1970: 1_699_999_900)\n        let snapshot = AlibabaCodingPlanUsageSnapshot(\n            planName: \"Lite\",\n            fiveHourUsedQuota: 70,\n            fiveHourTotalQuota: 1200,\n            fiveHourNextRefreshTime: stalePrimaryReset,\n            weeklyUsedQuota: 80,\n            weeklyTotalQuota: 9000,\n            weeklyNextRefreshTime: Date(timeIntervalSince1970: 1_700_010_000),\n            monthlyUsedQuota: 80,\n            monthlyTotalQuota: 18000,\n            monthlyNextRefreshTime: Date(timeIntervalSince1970: 1_700_100_000),\n            updatedAt: now)\n\n        let usage = snapshot.toUsageSnapshot()\n        #expect(usage.primary?.resetsAt == stalePrimaryReset.addingTimeInterval(TimeInterval(5 * 60 * 60)))\n    }\n}\n\n@Suite\nstruct AlibabaCodingPlanUsageParsingTests {\n    @Test\n    func parsesQuotaPayload() throws {\n        let now = Date(timeIntervalSince1970: 1_700_000_000)\n        let json = \"\"\"\n        {\n          \"data\": {\n            \"codingPlanInstanceInfos\": [\n              { \"planName\": \"Alibaba Coding Plan Pro\" }\n            ],\n            \"codingPlanQuotaInfo\": {\n              \"per5HourUsedQuota\": 52,\n              \"per5HourTotalQuota\": 1000,\n              \"per5HourQuotaNextRefreshTime\": 1700000300000,\n              \"perWeekUsedQuota\": 800,\n              \"perWeekTotalQuota\": 5000,\n              \"perWeekQuotaNextRefreshTime\": 1700100000000,\n              \"perBillMonthUsedQuota\": 1200,\n              \"perBillMonthTotalQuota\": 20000,\n              \"perBillMonthQuotaNextRefreshTime\": 1701000000000\n            }\n          },\n          \"status_code\": 0\n        }\n        \"\"\"\n\n        let snapshot = try AlibabaCodingPlanUsageFetcher.parseUsageSnapshot(from: Data(json.utf8), now: now)\n\n        #expect(snapshot.planName == \"Alibaba Coding Plan Pro\")\n        #expect(snapshot.fiveHourUsedQuota == 52)\n        #expect(snapshot.fiveHourTotalQuota == 1000)\n        #expect(snapshot.weeklyTotalQuota == 5000)\n        #expect(snapshot.monthlyTotalQuota == 20000)\n        #expect(snapshot.fiveHourNextRefreshTime == Date(timeIntervalSince1970: 1_700_000_300))\n    }\n\n    @Test\n    func multiInstanceQuotaPayloadUsesSelectedActiveInstancePlanName() throws {\n        let now = Date(timeIntervalSince1970: 1_700_000_000)\n        let json = \"\"\"\n        {\n          \"data\": {\n            \"codingPlanInstanceInfos\": [\n              {\n                \"planName\": \"Expired Starter\",\n                \"status\": \"EXPIRED\",\n                \"endTime\": \"2025-04-01 17:00\",\n                \"codingPlanQuotaInfo\": {\n                  \"per5HourUsedQuota\": 7,\n                  \"per5HourTotalQuota\": 100,\n                  \"per5HourQuotaNextRefreshTime\": 1700000100000\n                }\n              },\n              {\n                \"planName\": \"Active Pro\",\n                \"status\": \"VALID\",\n                \"codingPlanQuotaInfo\": {\n                  \"per5HourUsedQuota\": 52,\n                  \"per5HourTotalQuota\": 1000,\n                  \"per5HourQuotaNextRefreshTime\": 1700000300000\n                }\n              }\n            ]\n          },\n          \"status_code\": 0\n        }\n        \"\"\"\n\n        let snapshot = try AlibabaCodingPlanUsageFetcher.parseUsageSnapshot(from: Data(json.utf8), now: now)\n\n        #expect(snapshot.planName == \"Active Pro\")\n        #expect(snapshot.fiveHourUsedQuota == 52)\n        #expect(snapshot.fiveHourTotalQuota == 1000)\n        #expect(snapshot.fiveHourNextRefreshTime == Date(timeIntervalSince1970: 1_700_000_300))\n    }\n\n    @Test\n    func missingQuotaDataWithoutPositiveActiveSignalFails() {\n        let json = \"\"\"\n        {\n          \"data\": {\n            \"codingPlanInstanceInfos\": [\n              { \"planName\": \"Alibaba Coding Plan Pro\" }\n            ]\n          },\n          \"status_code\": 0\n        }\n        \"\"\"\n\n        #expect(throws: AlibabaCodingPlanUsageError.self) {\n            try AlibabaCodingPlanUsageFetcher.parseUsageSnapshot(from: Data(json.utf8))\n        }\n    }\n\n    @Test\n    func planUsageWithoutPositiveActiveProofFails() {\n        let json = \"\"\"\n        {\n          \"data\": {\n            \"codingPlanInstanceInfos\": [\n              {\n                \"planName\": \"Alibaba Coding Plan Pro\",\n                \"planUsage\": \"18%\"\n              }\n            ]\n          },\n          \"status_code\": 0\n        }\n        \"\"\"\n\n        #expect(throws: AlibabaCodingPlanUsageError.self) {\n            try AlibabaCodingPlanUsageFetcher.parseUsageSnapshot(from: Data(json.utf8))\n        }\n    }\n\n    @Test\n    func parsesWrappedJSONStringPayload() throws {\n        let now = Date(timeIntervalSince1970: 1_700_000_000)\n        let inner = \"\"\"\n        {\n          \"data\": {\n            \"codingPlanInstanceInfos\": [\n              {\n                \"planName\": \"Coding Plan Lite\",\n                \"status\": \"VALID\",\n                \"codingPlanQuotaInfo\": {\n                  \"per5HourUsedQuota\": 0,\n                  \"per5HourTotalQuota\": 1000,\n                  \"per5HourQuotaNextRefreshTime\": 1700000300000\n                }\n              }\n            ]\n          },\n          \"statusCode\": 200\n        }\n        \"\"\"\n            .replacingOccurrences(of: \"\\n\", with: \"\")\n            .replacingOccurrences(of: \"  \", with: \"\")\n            .replacingOccurrences(of: \"\\\"\", with: \"\\\\\\\"\")\n\n        let wrapped = \"\"\"\n        {\n          \"successResponse\": {\n            \"body\": \"\\(inner)\"\n          }\n        }\n        \"\"\"\n\n        let snapshot = try AlibabaCodingPlanUsageFetcher.parseUsageSnapshot(from: Data(wrapped.utf8), now: now)\n\n        #expect(snapshot.planName == \"Coding Plan Lite\")\n        #expect(snapshot.fiveHourTotalQuota == 1000)\n        #expect(snapshot.fiveHourUsedQuota == 0)\n    }\n\n    @Test\n    func planUsageFallbackStaysVisibleButNonQuantitative() throws {\n        let now = Date(timeIntervalSince1970: 1_700_000_000)\n        let json = \"\"\"\n        {\n          \"data\": {\n            \"codingPlanInstanceInfos\": [\n              {\n                \"planName\": \"Coding Plan Lite\",\n                \"status\": \"VALID\",\n                \"planUsage\": \"0%\",\n                \"endTime\": \"2026-04-01 17:00\"\n              }\n            ]\n          },\n          \"status_code\": 0\n        }\n        \"\"\"\n\n        let snapshot = try AlibabaCodingPlanUsageFetcher.parseUsageSnapshot(from: Data(json.utf8), now: now)\n\n        #expect(snapshot.planName == \"Coding Plan Lite\")\n        #expect(snapshot.fiveHourUsedQuota == nil)\n        #expect(snapshot.fiveHourTotalQuota == nil)\n        #expect(snapshot.fiveHourNextRefreshTime == nil)\n\n        let usage = snapshot.toUsageSnapshot()\n        #expect(usage.primary == nil)\n        #expect(usage.loginMethod(for: .alibaba) == \"Coding Plan Lite\")\n    }\n\n    @Test\n    func fallsBackToActivePlanWhenQuotaAndUsageMissing() throws {\n        let now = Date(timeIntervalSince1970: 1_700_000_000)\n        let json = \"\"\"\n        {\n          \"data\": {\n            \"codingPlanInstanceInfos\": [\n              {\n                \"planName\": \"Coding Plan Lite\",\n                \"status\": \"VALID\",\n                \"endTime\": \"2026-04-01 17:00\"\n              }\n            ]\n          },\n          \"status_code\": 0\n        }\n        \"\"\"\n\n        let snapshot = try AlibabaCodingPlanUsageFetcher.parseUsageSnapshot(from: Data(json.utf8), now: now)\n\n        #expect(snapshot.planName == \"Coding Plan Lite\")\n        #expect(snapshot.fiveHourUsedQuota == nil)\n        #expect(snapshot.fiveHourTotalQuota == nil)\n\n        let usage = snapshot.toUsageSnapshot()\n        #expect(usage.primary == nil)\n        #expect(usage.loginMethod(for: .alibaba) == \"Coding Plan Lite\")\n    }\n\n    @Test\n    func futureEndTimeCountsAsPositiveActiveSignal() throws {\n        let now = Date(timeIntervalSince1970: 1_700_000_000)\n        let json = \"\"\"\n        {\n          \"data\": {\n            \"codingPlanInstanceInfos\": [\n              {\n                \"planName\": \"Coding Plan Lite\",\n                \"endTime\": \"2030-04-01 17:00\"\n              }\n            ]\n          },\n          \"status_code\": 0\n        }\n        \"\"\"\n\n        let snapshot = try AlibabaCodingPlanUsageFetcher.parseUsageSnapshot(from: Data(json.utf8), now: now)\n\n        #expect(snapshot.planName == \"Coding Plan Lite\")\n        #expect(snapshot.fiveHourUsedQuota == nil)\n        #expect(snapshot.weeklyTotalQuota == nil)\n\n        let usage = snapshot.toUsageSnapshot()\n        #expect(usage.primary == nil)\n        #expect(usage.loginMethod(for: .alibaba) == \"Coding Plan Lite\")\n    }\n\n    @Test\n    func multiInstanceFallbackUsesSelectedActiveInstancePlanName() throws {\n        let now = Date(timeIntervalSince1970: 1_700_000_000)\n        let json = \"\"\"\n        {\n          \"data\": {\n            \"codingPlanInstanceInfos\": [\n              {\n                \"planName\": \"Expired Starter\",\n                \"status\": \"EXPIRED\",\n                \"endTime\": \"2025-04-01 17:00\"\n              },\n              {\n                \"planName\": \"Active Pro\",\n                \"status\": \"VALID\",\n                \"planUsage\": \"42%\"\n              }\n            ]\n          },\n          \"status_code\": 0\n        }\n        \"\"\"\n\n        let snapshot = try AlibabaCodingPlanUsageFetcher.parseUsageSnapshot(from: Data(json.utf8), now: now)\n\n        #expect(snapshot.planName == \"Active Pro\")\n        #expect(snapshot.fiveHourUsedQuota == nil)\n        #expect(snapshot.fiveHourTotalQuota == nil)\n\n        let usage = snapshot.toUsageSnapshot()\n        #expect(usage.primary == nil)\n        #expect(usage.loginMethod(for: .alibaba) == \"Active Pro\")\n    }\n\n    @Test\n    func activeInstanceWithoutQuotaDoesNotBorrowQuotaFromAnotherInstance() throws {\n        let now = Date(timeIntervalSince1970: 1_700_000_000)\n        let json = \"\"\"\n        {\n          \"data\": {\n            \"codingPlanInstanceInfos\": [\n              {\n                \"planName\": \"Expired Starter\",\n                \"status\": \"EXPIRED\",\n                \"endTime\": \"2025-04-01 17:00\",\n                \"codingPlanQuotaInfo\": {\n                  \"per5HourUsedQuota\": 7,\n                  \"per5HourTotalQuota\": 100,\n                  \"per5HourQuotaNextRefreshTime\": 1700000100000\n                }\n              },\n              {\n                \"planName\": \"Active Pro\",\n                \"status\": \"VALID\"\n              }\n            ]\n          },\n          \"status_code\": 0\n        }\n        \"\"\"\n\n        let snapshot = try AlibabaCodingPlanUsageFetcher.parseUsageSnapshot(from: Data(json.utf8), now: now)\n\n        #expect(snapshot.planName == \"Active Pro\")\n        #expect(snapshot.fiveHourUsedQuota == nil)\n        #expect(snapshot.fiveHourTotalQuota == nil)\n        #expect(snapshot.fiveHourNextRefreshTime == nil)\n    }\n\n    @Test\n    func payloadLevelActiveProofDoesNotLabelFirstInstanceWhenNoInstanceIsActive() {\n        let now = Date(timeIntervalSince1970: 1_700_000_000)\n        let json = \"\"\"\n        {\n          \"data\": {\n            \"status\": \"VALID\",\n            \"codingPlanInstanceInfos\": [\n              {\n                \"planName\": \"Expired Starter\",\n                \"status\": \"EXPIRED\",\n                \"endTime\": \"2025-04-01 17:00\"\n              },\n              {\n                \"planName\": \"No Proof Pro\"\n              }\n            ]\n          },\n          \"status_code\": 0\n        }\n        \"\"\"\n\n        #expect(throws: AlibabaCodingPlanUsageError.self) {\n            try AlibabaCodingPlanUsageFetcher.parseUsageSnapshot(from: Data(json.utf8), now: now)\n        }\n    }\n\n    @Test\n    func doesNotFallbackForInactivePlanWithoutQuota() {\n        let json = \"\"\"\n        {\n          \"data\": {\n            \"codingPlanInstanceInfos\": [\n              {\n                \"planName\": \"Coding Plan Lite\",\n                \"status\": \"EXPIRED\"\n              }\n            ]\n          },\n          \"status_code\": 0\n        }\n        \"\"\"\n\n        #expect(throws: AlibabaCodingPlanUsageError.self) {\n            try AlibabaCodingPlanUsageFetcher.parseUsageSnapshot(from: Data(json.utf8))\n        }\n    }\n\n    @Test\n    func consoleNeedLoginPayloadMapsToLoginRequired() {\n        let json = \"\"\"\n        {\n          \"code\": \"ConsoleNeedLogin\",\n          \"message\": \"You need to log in.\",\n          \"requestId\": \"abc\",\n          \"successResponse\": false\n        }\n        \"\"\"\n\n        #expect(throws: AlibabaCodingPlanUsageError.loginRequired) {\n            try AlibabaCodingPlanUsageFetcher.parseUsageSnapshot(from: Data(json.utf8))\n        }\n    }\n\n    @Test\n    func consoleNeedLoginPayloadMapsToApiErrorForAPIKeyMode() {\n        let json = \"\"\"\n        {\n          \"code\": \"ConsoleNeedLogin\",\n          \"message\": \"You need to log in.\",\n          \"requestId\": \"abc\",\n          \"successResponse\": false\n        }\n        \"\"\"\n\n        do {\n            _ = try AlibabaCodingPlanUsageFetcher.parseUsageSnapshot(\n                from: Data(json.utf8),\n                authMode: .apiKey)\n            Issue.record(\"Expected API-mode ConsoleNeedLogin payload to throw\")\n        } catch let error as AlibabaCodingPlanUsageError {\n            guard case let .apiError(message) = error else {\n                Issue.record(\"Expected apiError, got \\(error)\")\n                return\n            }\n            #expect(message.contains(\"requires a console session\"))\n        } catch {\n            Issue.record(\"Expected AlibabaCodingPlanUsageError, got \\(error)\")\n        }\n    }\n}\n\n@Suite(.serialized)\nstruct AlibabaCodingPlanFallbackTests {\n    private struct StubClaudeFetcher: ClaudeUsageFetching {\n        func loadLatestUsage(model _: String) async throws -> ClaudeUsageSnapshot {\n            throw ClaudeUsageError.parseFailed(\"stub\")\n        }\n\n        func debugRawProbe(model _: String) async -> String {\n            \"stub\"\n        }\n\n        func detectVersion() -> String? {\n            nil\n        }\n    }\n\n    private func makeContext(\n        sourceMode: ProviderSourceMode,\n        settings: ProviderSettingsSnapshot? = nil,\n        env: [String: String] = [:]) -> ProviderFetchContext\n    {\n        let browserDetection = BrowserDetection(cacheTTL: 0)\n        return ProviderFetchContext(\n            runtime: .cli,\n            sourceMode: sourceMode,\n            includeCredits: false,\n            webTimeout: 1,\n            webDebugDumpHTML: false,\n            verbose: false,\n            env: env,\n            settings: settings,\n            fetcher: UsageFetcher(environment: env),\n            claudeFetcher: StubClaudeFetcher(),\n            browserDetection: browserDetection)\n    }\n\n    @Test\n    func fallsBackOnTLSFailureInAutoMode() {\n        let strategy = AlibabaCodingPlanWebFetchStrategy()\n        let context = self.makeContext(sourceMode: .auto)\n        #expect(strategy.shouldFallback(on: URLError(.secureConnectionFailed), context: context))\n    }\n\n    @Test\n    func doesNotFallbackOnTLSFailureWhenSourceForcedToWeb() {\n        let strategy = AlibabaCodingPlanWebFetchStrategy()\n        let context = self.makeContext(sourceMode: .web)\n        #expect(strategy.shouldFallback(on: URLError(.secureConnectionFailed), context: context) == false)\n    }\n\n    @Test\n    func autoModeDoesNotBorrowManualCookieAuthorityWhenBrowserImportFails() {\n        let strategy = AlibabaCodingPlanWebFetchStrategy()\n        let settings = ProviderSettingsSnapshot.make(\n            alibaba: ProviderSettingsSnapshot.AlibabaCodingPlanProviderSettings(\n                cookieSource: .auto,\n                manualCookieHeader: \"session=manual-cookie\",\n                apiRegion: .international))\n        let context = self.makeContext(sourceMode: .auto, settings: settings)\n\n        CookieHeaderCache.clear(provider: .alibaba)\n        AlibabaCodingPlanCookieImporter.importSessionOverrideForTesting = { _, _ in\n            throw AlibabaCodingPlanSettingsError.missingCookie()\n        }\n        defer {\n            AlibabaCodingPlanCookieImporter.importSessionOverrideForTesting = nil\n        }\n\n        do {\n            _ = try AlibabaCodingPlanWebFetchStrategy.resolveCookieHeader(context: context, allowCached: false)\n            Issue.record(\"Expected auto mode to fail instead of borrowing the manual cookie header\")\n        } catch let error as AlibabaCodingPlanSettingsError {\n            guard case .missingCookie = error else {\n                Issue.record(\"Expected missingCookie, got \\(error)\")\n                return\n            }\n            #expect(strategy.shouldFallback(on: error, context: context))\n        } catch {\n            Issue.record(\"Expected AlibabaCodingPlanSettingsError, got \\(error)\")\n        }\n    }\n\n    @Test\n    func autoModeSkipsWebWhenNoAlibabaSessionIsAvailable() async {\n        let strategy = AlibabaCodingPlanWebFetchStrategy()\n        let settings = ProviderSettingsSnapshot.make(\n            alibaba: ProviderSettingsSnapshot.AlibabaCodingPlanProviderSettings(\n                cookieSource: .auto,\n                manualCookieHeader: nil,\n                apiRegion: .international))\n        let context = self.makeContext(\n            sourceMode: .auto,\n            settings: settings,\n            env: [AlibabaCodingPlanSettingsReader.apiTokenKey: \"token-abc\"])\n\n        #expect(await strategy.isAvailable(context) == false)\n    }\n}\n\n@Suite\nstruct AlibabaCodingPlanRegionTests {\n    @Test\n    func defaultsToInternationalEndpoint() {\n        let url = AlibabaCodingPlanUsageFetcher.resolveQuotaURL(region: .international, environment: [:])\n        #expect(url.host == \"modelstudio.console.alibabacloud.com\")\n        #expect(url.path == \"/data/api.json\")\n    }\n\n    @Test\n    func usesChinaMainlandHost() {\n        let url = AlibabaCodingPlanUsageFetcher.resolveQuotaURL(region: .chinaMainland, environment: [:])\n        #expect(url.host == \"bailian.console.aliyun.com\")\n    }\n\n    @Test\n    func hostOverrideWinsForQuotaURL() {\n        let env = [AlibabaCodingPlanSettingsReader.hostKey: \"custom.aliyun.com\"]\n        let url = AlibabaCodingPlanUsageFetcher.resolveQuotaURL(region: .international, environment: env)\n        #expect(url.host == \"custom.aliyun.com\")\n        #expect(url.path == \"/data/api.json\")\n    }\n\n    @Test\n    func hostOverrideUsesSelectedRegionForQuotaURL() {\n        let env = [AlibabaCodingPlanSettingsReader.hostKey: \"custom.aliyun.com\"]\n        let url = AlibabaCodingPlanUsageFetcher.resolveQuotaURL(region: .chinaMainland, environment: env)\n        #expect(url.host == \"custom.aliyun.com\")\n\n        let components = URLComponents(url: url, resolvingAgainstBaseURL: false)\n        let currentRegion = components?.queryItems?.first(where: { $0.name == \"currentRegionId\" })?.value\n        #expect(currentRegion == AlibabaCodingPlanAPIRegion.chinaMainland.currentRegionID)\n    }\n\n    @Test\n    func bareHostOverrideBuildsConsoleDashboardURL() {\n        let env = [AlibabaCodingPlanSettingsReader.hostKey: \"custom.aliyun.com\"]\n        let url = AlibabaCodingPlanUsageFetcher.resolveConsoleDashboardURL(region: .international, environment: env)\n        #expect(url.scheme == \"https\")\n        #expect(url.host == \"custom.aliyun.com\")\n        #expect(url.path == AlibabaCodingPlanAPIRegion.international.dashboardURL.path)\n\n        let components = URLComponents(url: url, resolvingAgainstBaseURL: false)\n        let tab = components?.queryItems?.first(where: { $0.name == \"tab\" })?.value\n        #expect(tab == \"coding-plan\")\n    }\n\n    @Test\n    func quotaUrlOverrideBeatsHost() {\n        let env = [AlibabaCodingPlanSettingsReader.quotaURLKey: \"https://example.com/custom/quota\"]\n        let url = AlibabaCodingPlanUsageFetcher.resolveQuotaURL(region: .international, environment: env)\n        #expect(url.absoluteString == \"https://example.com/custom/quota\")\n    }\n}\n\n@Suite(.serialized)\nstruct AlibabaCodingPlanUsageFetcherRequestTests {\n    @Test\n    func api401MapsToInvalidCredentials() async throws {\n        let registered = URLProtocol.registerClass(AlibabaUsageFetcherStubURLProtocol.self)\n        defer {\n            if registered {\n                URLProtocol.unregisterClass(AlibabaUsageFetcherStubURLProtocol.self)\n            }\n            AlibabaUsageFetcherStubURLProtocol.handler = nil\n        }\n\n        AlibabaUsageFetcherStubURLProtocol.handler = { request in\n            guard let url = request.url else { throw URLError(.badURL) }\n            return Self.makeResponse(url: url, body: #\"{\"message\":\"unauthorized\"}\"#, statusCode: 401)\n        }\n\n        await #expect(throws: AlibabaCodingPlanUsageError.invalidCredentials) {\n            _ = try await AlibabaCodingPlanUsageFetcher.fetchUsage(\n                apiKey: \"cpk-test\",\n                region: .chinaMainland,\n                environment: [AlibabaCodingPlanSettingsReader.quotaURLKey: \"https://alibaba-api.test/data/api.json\"])\n        }\n    }\n\n    @Test\n    func cookieSECTokenFallbackSurvivesUserInfoRequestFailure() async throws {\n        let registered = URLProtocol.registerClass(AlibabaConsoleSECTokenStubURLProtocol.self)\n        defer {\n            if registered {\n                URLProtocol.unregisterClass(AlibabaConsoleSECTokenStubURLProtocol.self)\n            }\n            AlibabaConsoleSECTokenStubURLProtocol.handler = nil\n        }\n\n        AlibabaConsoleSECTokenStubURLProtocol.handler = { request in\n            guard let url = request.url else { throw URLError(.badURL) }\n\n            if url.host == \"modelstudio.console.alibabacloud.com\", request.httpMethod == \"GET\" {\n                return Self.makeResponse(url: url, body: \"<html></html>\", statusCode: 200)\n            }\n\n            if url.host == \"modelstudio.console.alibabacloud.com\", url.path == \"/tool/user/info.json\" {\n                throw URLError(.timedOut)\n            }\n\n            if url.host == \"bailian-singapore-cs.alibabacloud.com\", request.httpMethod == \"POST\" {\n                let body = Self.requestBodyString(from: request)\n                #expect(body.contains(\"sec_token=cookie-sec-token\"))\n                let json = \"\"\"\n                {\n                  \"data\": {\n                    \"codingPlanInstanceInfos\": [\n                      { \"planName\": \"Alibaba Coding Plan Pro\", \"status\": \"VALID\" }\n                    ],\n                    \"codingPlanQuotaInfo\": {\n                      \"per5HourUsedQuota\": 52,\n                      \"per5HourTotalQuota\": 1000,\n                      \"per5HourQuotaNextRefreshTime\": 1700000300000\n                    }\n                  },\n                  \"status_code\": 0\n                }\n                \"\"\"\n                return Self.makeResponse(url: url, body: json, statusCode: 200)\n            }\n\n            throw URLError(.unsupportedURL)\n        }\n\n        let snapshot = try await AlibabaCodingPlanUsageFetcher.fetchUsage(\n            cookieHeader: \"sec_token=cookie-sec-token; login_aliyunid_ticket=ticket; login_aliyunid_pk=user\",\n            region: .international,\n            environment: [:],\n            now: Date(timeIntervalSince1970: 1_700_000_000))\n\n        #expect(snapshot.planName == \"Alibaba Coding Plan Pro\")\n        #expect(snapshot.fiveHourUsedQuota == 52)\n        #expect(snapshot.fiveHourTotalQuota == 1000)\n    }\n\n    @Test\n    func hostOverrideAppliesToUserInfoSECTokenFallback() async throws {\n        let registered = URLProtocol.registerClass(AlibabaConsoleSECTokenStubURLProtocol.self)\n        defer {\n            if registered {\n                URLProtocol.unregisterClass(AlibabaConsoleSECTokenStubURLProtocol.self)\n            }\n            AlibabaConsoleSECTokenStubURLProtocol.handler = nil\n        }\n\n        AlibabaConsoleSECTokenStubURLProtocol.handler = { request in\n            guard let url = request.url else { throw URLError(.badURL) }\n            #expect(url.host == \"alibaba-proxy.test\")\n\n            if request.httpMethod == \"GET\", url.path == AlibabaCodingPlanAPIRegion.international.dashboardURL.path {\n                return Self.makeResponse(url: url, body: \"<html></html>\", statusCode: 200)\n            }\n\n            if request.httpMethod == \"GET\", url.path == \"/tool/user/info.json\" {\n                return Self.makeResponse(\n                    url: url,\n                    body: #\"{\"data\":{\"secToken\":\"override-sec-token\"}}\"#,\n                    statusCode: 200)\n            }\n\n            if request.httpMethod == \"POST\", url.path == \"/data/api.json\" {\n                let body = Self.requestBodyString(from: request)\n                #expect(body.contains(\"sec_token=override-sec-token\"))\n                let json = \"\"\"\n                {\n                  \"data\": {\n                    \"codingPlanInstanceInfos\": [\n                      { \"planName\": \"Alibaba Coding Plan Pro\", \"status\": \"VALID\" }\n                    ],\n                    \"codingPlanQuotaInfo\": {\n                      \"per5HourUsedQuota\": 21,\n                      \"per5HourTotalQuota\": 1000,\n                      \"per5HourQuotaNextRefreshTime\": 1700000300000\n                    }\n                  },\n                  \"status_code\": 0\n                }\n                \"\"\"\n                return Self.makeResponse(url: url, body: json, statusCode: 200)\n            }\n\n            throw URLError(.unsupportedURL)\n        }\n\n        let snapshot = try await AlibabaCodingPlanUsageFetcher.fetchUsage(\n            cookieHeader: \"sec_token=cookie-sec-token; login_aliyunid_ticket=ticket; login_aliyunid_pk=user\",\n            region: .international,\n            environment: [AlibabaCodingPlanSettingsReader.hostKey: \"https://alibaba-proxy.test\"],\n            now: Date(timeIntervalSince1970: 1_700_000_000))\n\n        #expect(snapshot.planName == \"Alibaba Coding Plan Pro\")\n        #expect(snapshot.fiveHourUsedQuota == 21)\n        #expect(snapshot.fiveHourTotalQuota == 1000)\n    }\n\n    @Test\n    func consoleRequestBodyUsesRegionSpecificMetadata() async throws {\n        let registered = URLProtocol.registerClass(AlibabaConsoleSECTokenStubURLProtocol.self)\n        defer {\n            if registered {\n                URLProtocol.unregisterClass(AlibabaConsoleSECTokenStubURLProtocol.self)\n            }\n            AlibabaConsoleSECTokenStubURLProtocol.handler = nil\n        }\n\n        AlibabaConsoleSECTokenStubURLProtocol.handler = { request in\n            guard let url = request.url else { throw URLError(.badURL) }\n\n            if request.httpMethod == \"GET\", url.path == AlibabaCodingPlanAPIRegion.chinaMainland.dashboardURL.path {\n                return Self.makeResponse(url: url, body: \"<html></html>\", statusCode: 200)\n            }\n\n            if request.httpMethod == \"GET\", url.path == \"/tool/user/info.json\" {\n                return Self.makeResponse(url: url, body: #\"{\"data\":{\"secToken\":\"cn-sec-token\"}}\"#, statusCode: 200)\n            }\n\n            if request.httpMethod == \"POST\", url.path == \"/data/api.json\" {\n                let body = Self.requestBodyString(from: request)\n                let params = try #require(Self.requestParamsDictionary(from: body))\n                let data = try #require(params[\"Data\"] as? [String: Any])\n                let cornerstone = try #require(data[\"cornerstoneParam\"] as? [String: Any])\n                #expect(cornerstone[\"domain\"] as? String == AlibabaCodingPlanAPIRegion.chinaMainland.consoleDomain)\n                #expect(cornerstone[\"consoleSite\"] as? String == AlibabaCodingPlanAPIRegion.chinaMainland.consoleSite)\n                #expect(\n                    cornerstone[\"feURL\"] as? String\n                        == AlibabaCodingPlanAPIRegion.chinaMainland.dashboardURL.absoluteString)\n\n                let json = \"\"\"\n                {\n                  \"data\": {\n                    \"codingPlanInstanceInfos\": [\n                      { \"planName\": \"Alibaba Coding Plan Pro\", \"status\": \"VALID\" }\n                    ],\n                    \"codingPlanQuotaInfo\": {\n                      \"per5HourUsedQuota\": 21,\n                      \"per5HourTotalQuota\": 1000,\n                      \"per5HourQuotaNextRefreshTime\": 1700000300000\n                    }\n                  },\n                  \"status_code\": 0\n                }\n                \"\"\"\n                return Self.makeResponse(url: url, body: json, statusCode: 200)\n            }\n\n            throw URLError(.unsupportedURL)\n        }\n\n        let snapshot = try await AlibabaCodingPlanUsageFetcher.fetchUsage(\n            cookieHeader: \"sec_token=cookie-sec-token; login_aliyunid_ticket=ticket; login_aliyunid_pk=user\",\n            region: .chinaMainland,\n            environment: [:],\n            now: Date(timeIntervalSince1970: 1_700_000_000))\n\n        #expect(snapshot.planName == \"Alibaba Coding Plan Pro\")\n        #expect(snapshot.fiveHourUsedQuota == 21)\n        #expect(snapshot.fiveHourTotalQuota == 1000)\n    }\n\n    private static func makeResponse(url: URL, body: String, statusCode: Int) -> (HTTPURLResponse, Data) {\n        let response = HTTPURLResponse(\n            url: url,\n            statusCode: statusCode,\n            httpVersion: \"HTTP/1.1\",\n            headerFields: [\"Content-Type\": \"application/json\"])!\n        return (response, Data(body.utf8))\n    }\n\n    private static func requestBodyString(from request: URLRequest) -> String {\n        if let data = request.httpBody {\n            return String(data: data, encoding: .utf8) ?? \"\"\n        }\n\n        guard let stream = request.httpBodyStream else {\n            return \"\"\n        }\n\n        stream.open()\n        defer { stream.close() }\n\n        var data = Data()\n        let bufferSize = 4096\n        let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize)\n        defer { buffer.deallocate() }\n\n        while stream.hasBytesAvailable {\n            let count = stream.read(buffer, maxLength: bufferSize)\n            if count <= 0 {\n                break\n            }\n            data.append(buffer, count: count)\n        }\n\n        return String(data: data, encoding: .utf8) ?? \"\"\n    }\n\n    private static func requestParamsDictionary(from body: String) -> [String: Any]? {\n        guard var components = URLComponents(string: \"https://example.invalid/?\\(body)\"),\n              let params = components.queryItems?.first(where: { $0.name == \"params\" })?.value,\n              let data = params.data(using: .utf8)\n        else {\n            return nil\n        }\n\n        let object = try? JSONSerialization.jsonObject(with: data, options: [])\n        return object as? [String: Any]\n    }\n}\n\nfinal class AlibabaUsageFetcherStubURLProtocol: URLProtocol {\n    nonisolated(unsafe) static var handler: ((URLRequest) throws -> (HTTPURLResponse, Data))?\n\n    override static func canInit(with request: URLRequest) -> Bool {\n        request.url?.host == \"alibaba-api.test\"\n    }\n\n    override static func canonicalRequest(for request: URLRequest) -> URLRequest {\n        request\n    }\n\n    override func startLoading() {\n        guard let handler = Self.handler else {\n            self.client?.urlProtocol(self, didFailWithError: URLError(.badServerResponse))\n            return\n        }\n\n        do {\n            let (response, data) = try handler(self.request)\n            self.client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)\n            self.client?.urlProtocol(self, didLoad: data)\n            self.client?.urlProtocolDidFinishLoading(self)\n        } catch {\n            self.client?.urlProtocol(self, didFailWithError: error)\n        }\n    }\n\n    override func stopLoading() {}\n}\n\nfinal class AlibabaConsoleSECTokenStubURLProtocol: URLProtocol {\n    nonisolated(unsafe) static var handler: ((URLRequest) throws -> (HTTPURLResponse, Data))?\n\n    override static func canInit(with request: URLRequest) -> Bool {\n        guard let host = request.url?.host else { return false }\n        return [\n            \"alibaba-proxy.test\",\n            \"modelstudio.console.alibabacloud.com\",\n            \"bailian-singapore-cs.alibabacloud.com\",\n            \"bailian.console.aliyun.com\",\n            \"bailian-beijing-cs.aliyuncs.com\",\n        ].contains(host)\n    }\n\n    override static func canonicalRequest(for request: URLRequest) -> URLRequest {\n        request\n    }\n\n    override func startLoading() {\n        guard let handler = Self.handler else {\n            self.client?.urlProtocol(self, didFailWithError: URLError(.badServerResponse))\n            return\n        }\n\n        do {\n            let (response, data) = try handler(self.request)\n            self.client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)\n            self.client?.urlProtocol(self, didLoad: data)\n            self.client?.urlProtocolDidFinishLoading(self)\n        } catch {\n            self.client?.urlProtocol(self, didFailWithError: error)\n        }\n    }\n\n    override func stopLoading() {}\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/AmpUsageFetcherTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\nstruct AmpUsageFetcherTests {\n    @Test\n    func `attaches cookie for amp hosts`() {\n        #expect(AmpUsageFetcher.shouldAttachCookie(to: URL(string: \"https://ampcode.com/settings\")))\n        #expect(AmpUsageFetcher.shouldAttachCookie(to: URL(string: \"https://www.ampcode.com\")))\n        #expect(AmpUsageFetcher.shouldAttachCookie(to: URL(string: \"https://app.ampcode.com/path\")))\n    }\n\n    @Test\n    func `rejects non amp hosts`() {\n        #expect(!AmpUsageFetcher.shouldAttachCookie(to: URL(string: \"https://example.com\")))\n        #expect(!AmpUsageFetcher.shouldAttachCookie(to: URL(string: \"https://ampcode.com.evil.com\")))\n        #expect(!AmpUsageFetcher.shouldAttachCookie(to: nil))\n    }\n\n    @Test\n    func `detects login redirects`() throws {\n        let signIn = try #require(URL(string: \"https://ampcode.com/auth/sign-in?returnTo=%2Fsettings\"))\n        #expect(AmpUsageFetcher.isLoginRedirect(signIn))\n\n        let sso = try #require(URL(string: \"https://ampcode.com/auth/sso?returnTo=%2Fsettings\"))\n        #expect(AmpUsageFetcher.isLoginRedirect(sso))\n\n        let login = try #require(URL(string: \"https://ampcode.com/login\"))\n        #expect(AmpUsageFetcher.isLoginRedirect(login))\n\n        let signin = try #require(URL(string: \"https://www.ampcode.com/signin\"))\n        #expect(AmpUsageFetcher.isLoginRedirect(signin))\n    }\n\n    @Test\n    func `ignores non login UR ls`() throws {\n        let settings = try #require(URL(string: \"https://ampcode.com/settings\"))\n        #expect(!AmpUsageFetcher.isLoginRedirect(settings))\n\n        let signOut = try #require(URL(string: \"https://ampcode.com/auth/sign-out\"))\n        #expect(!AmpUsageFetcher.isLoginRedirect(signOut))\n\n        let evil = try #require(URL(string: \"https://ampcode.com.evil.com/auth/sign-in\"))\n        #expect(!AmpUsageFetcher.isLoginRedirect(evil))\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/AmpUsageParserTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\nstruct AmpUsageParserTests {\n    @Test\n    func `parses free tier usage from settings HTML`() throws {\n        let now = Date(timeIntervalSince1970: 1_700_000_000)\n        let html = \"\"\"\n        <script>\n        __sveltekit_x.data = {user:{},\n        freeTierUsage:{bucket:\"ubi\",quota:1000,hourlyReplenishment:42,windowHours:24,used:338.5}};\n        </script>\n        \"\"\"\n\n        let snapshot = try AmpUsageParser.parse(html: html, now: now)\n\n        #expect(snapshot.freeQuota == 1000)\n        #expect(snapshot.freeUsed == 338.5)\n        #expect(snapshot.hourlyReplenishment == 42)\n        #expect(snapshot.windowHours == 24)\n\n        let usage = snapshot.toUsageSnapshot(now: now)\n        let expectedPercent = (338.5 / 1000) * 100\n        #expect(abs((usage.primary?.usedPercent ?? 0) - expectedPercent) < 0.001)\n        #expect(usage.primary?.windowMinutes == 1440)\n\n        let expectedHoursToFull = 338.5 / 42\n        let expectedReset = now.addingTimeInterval(expectedHoursToFull * 3600)\n        #expect(usage.primary?.resetsAt == expectedReset)\n        #expect(usage.identity?.loginMethod == \"Amp Free\")\n    }\n\n    @Test\n    func `parses free tier usage from prefetched key`() throws {\n        let now = Date(timeIntervalSince1970: 1_700_010_000)\n        let html = \"\"\"\n        <script>\n        __sveltekit_x.data = {\n          \"w6b2h6/getFreeTierUsage/\":{bucket:\"ubi\",quota:1000,hourlyReplenishment:42,windowHours:24,used:0}\n        };\n        </script>\n        \"\"\"\n\n        let snapshot = try AmpUsageParser.parse(html: html, now: now)\n        #expect(snapshot.freeUsed == 0)\n        #expect(snapshot.freeQuota == 1000)\n    }\n\n    @Test\n    func `missing usage throws parse failed`() {\n        let html = \"<html><body>No usage here.</body></html>\"\n\n        #expect {\n            try AmpUsageParser.parse(html: html)\n        } throws: { error in\n            guard case let AmpUsageError.parseFailed(message) = error else { return false }\n            return message.contains(\"Missing Amp Free usage data\")\n        }\n    }\n\n    @Test\n    func `signed out throws not logged in`() {\n        let html = \"<html><body>Please sign in to Amp.</body></html>\"\n\n        #expect {\n            try AmpUsageParser.parse(html: html)\n        } throws: { error in\n            guard case AmpUsageError.notLoggedIn = error else { return false }\n            return true\n        }\n    }\n\n    @Test\n    func `usage snapshot clamps percent and window`() {\n        let now = Date(timeIntervalSince1970: 1_700_020_000)\n        let snapshot = AmpUsageSnapshot(\n            freeQuota: 100,\n            freeUsed: 150,\n            hourlyReplenishment: 10,\n            windowHours: nil,\n            updatedAt: now)\n\n        let usage = snapshot.toUsageSnapshot(now: now)\n        #expect(usage.primary?.usedPercent == 100)\n        #expect(usage.primary?.windowMinutes == nil)\n    }\n\n    @Test\n    func `usage snapshot omits reset when hourly replenishment is zero`() {\n        let now = Date(timeIntervalSince1970: 1_700_030_000)\n        let snapshot = AmpUsageSnapshot(\n            freeQuota: 100,\n            freeUsed: 20,\n            hourlyReplenishment: 0,\n            windowHours: 24,\n            updatedAt: now)\n\n        let usage = snapshot.toUsageSnapshot(now: now)\n        #expect(usage.primary?.resetsAt == nil)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/AntigravityStatusProbeTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n\nstruct AntigravityStatusProbeTests {\n    @Test\n    func `parses user status response`() throws {\n        let json = \"\"\"\n        {\n          \"code\": 0,\n          \"userStatus\": {\n            \"email\": \"test@example.com\",\n            \"planStatus\": {\n              \"planInfo\": {\n                \"planName\": \"Pro\"\n              }\n            },\n            \"cascadeModelConfigData\": {\n              \"clientModelConfigs\": [\n                {\n                  \"label\": \"Claude 3.5 Sonnet\",\n                  \"modelOrAlias\": { \"model\": \"claude-3-5-sonnet\" },\n                  \"quotaInfo\": { \"remainingFraction\": 0.5, \"resetTime\": \"2025-12-24T10:00:00Z\" }\n                },\n                {\n                  \"label\": \"Gemini Pro Low\",\n                  \"modelOrAlias\": { \"model\": \"gemini-pro-low\" },\n                  \"quotaInfo\": { \"remainingFraction\": 0.8, \"resetTime\": \"2025-12-24T11:00:00Z\" }\n                },\n                {\n                  \"label\": \"Gemini Flash\",\n                  \"modelOrAlias\": { \"model\": \"gemini-flash\" },\n                  \"quotaInfo\": { \"remainingFraction\": 0.2, \"resetTime\": \"2025-12-24T12:00:00Z\" }\n                }\n              ]\n            }\n          }\n        }\n        \"\"\"\n\n        let data = Data(json.utf8)\n        let snapshot = try AntigravityStatusProbe.parseUserStatusResponse(data)\n        #expect(snapshot.accountEmail == \"test@example.com\")\n        #expect(snapshot.accountPlan == \"Pro\")\n        #expect(snapshot.modelQuotas.count == 3)\n\n        let usage = try snapshot.toUsageSnapshot()\n        guard let primary = usage.primary else {\n            return\n        }\n        #expect(primary.remainingPercent.rounded() == 50)\n        #expect(usage.secondary?.remainingPercent.rounded() == 80)\n        #expect(usage.tertiary?.remainingPercent.rounded() == 20)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/AppDelegateTests.swift",
    "content": "import AppKit\nimport CodexBarCore\nimport Testing\n@testable import CodexBar\n\n@MainActor\nstruct AppDelegateTests {\n    @Test\n    func `builds status controller after launch`() {\n        let appDelegate = AppDelegate()\n        var factoryCalls = 0\n\n        // Install a test factory that records invocations without touching NSStatusBar.\n        StatusItemController.factory = { _, _, _, _, _ in\n            factoryCalls += 1\n            return DummyStatusController()\n        }\n        defer { StatusItemController.factory = StatusItemController.defaultFactory }\n\n        let settings = SettingsStore(\n            configStore: testConfigStore(suiteName: \"AppDelegateTests\"),\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n        let account = fetcher.loadAccountInfo()\n\n        // configure should not eagerly construct the status controller\n        appDelegate.configure(store: store, settings: settings, account: account, selection: PreferencesSelection())\n        #expect(factoryCalls == 0)\n\n        // construction happens once after launch\n        appDelegate.applicationDidFinishLaunching(Notification(name: NSApplication.didFinishLaunchingNotification))\n        #expect(factoryCalls == 1)\n\n        // idempotent on subsequent calls\n        appDelegate.applicationDidFinishLaunching(Notification(name: NSApplication.didFinishLaunchingNotification))\n        #expect(factoryCalls == 1)\n    }\n}\n\n@MainActor\nprivate final class DummyStatusController: StatusItemControlling {\n    func openMenuFromShortcut() {}\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/AugmentCLIFetchStrategyFallbackTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\n#if os(macOS)\n\n/// Regression tests for #474: verify that CLI timeout errors trigger fallback\n/// to the web strategy instead of stalling the refresh cycle.\nstruct AugmentCLIFetchStrategyFallbackTests {\n    private struct StubClaudeFetcher: ClaudeUsageFetching {\n        func loadLatestUsage(model _: String) async throws -> ClaudeUsageSnapshot {\n            throw ClaudeUsageError.parseFailed(\"stub\")\n        }\n\n        func debugRawProbe(model _: String) async -> String {\n            \"stub\"\n        }\n\n        func detectVersion() -> String? {\n            nil\n        }\n    }\n\n    private func makeContext(sourceMode: ProviderSourceMode = .auto) -> ProviderFetchContext {\n        let env: [String: String] = [:]\n        return ProviderFetchContext(\n            runtime: .app,\n            sourceMode: sourceMode,\n            includeCredits: false,\n            webTimeout: 1,\n            webDebugDumpHTML: false,\n            verbose: false,\n            env: env,\n            settings: nil,\n            fetcher: UsageFetcher(environment: env),\n            claudeFetcher: StubClaudeFetcher(),\n            browserDetection: BrowserDetection(cacheTTL: 0))\n    }\n\n    // SubprocessRunnerError is not an AuggieCLIError, so it hits the default\n    // fallback=true path — the desired behavior for infrastructure errors.\n\n    @Test\n    func `timeout error falls back to web`() {\n        let strategy = AugmentCLIFetchStrategy()\n        let context = self.makeContext()\n        let error = SubprocessRunnerError.timedOut(\"auggie-account-status\")\n        #expect(strategy.shouldFallback(on: error, context: context) == true)\n    }\n\n    @Test\n    func `binary not found falls back to web`() {\n        let strategy = AugmentCLIFetchStrategy()\n        let context = self.makeContext()\n        let error = SubprocessRunnerError.binaryNotFound(\"/usr/local/bin/auggie\")\n        #expect(strategy.shouldFallback(on: error, context: context) == true)\n    }\n\n    @Test\n    func `launch failed falls back to web`() {\n        let strategy = AugmentCLIFetchStrategy()\n        let context = self.makeContext()\n        let error = SubprocessRunnerError.launchFailed(\"permission denied\")\n        #expect(strategy.shouldFallback(on: error, context: context) == true)\n    }\n\n    @Test\n    func `not authenticated falls back to web`() {\n        let strategy = AugmentCLIFetchStrategy()\n        let context = self.makeContext()\n        #expect(strategy.shouldFallback(on: AuggieCLIError.notAuthenticated, context: context) == true)\n    }\n\n    @Test\n    func `no output falls back to web`() {\n        let strategy = AugmentCLIFetchStrategy()\n        let context = self.makeContext()\n        #expect(strategy.shouldFallback(on: AuggieCLIError.noOutput, context: context) == true)\n    }\n\n    @Test\n    func `parse error does not fall back`() {\n        let strategy = AugmentCLIFetchStrategy()\n        let context = self.makeContext()\n        #expect(strategy.shouldFallback(on: AuggieCLIError.parseError(\"bad data\"), context: context) == false)\n    }\n\n    @Test\n    func `non zero exit falls back to web`() {\n        let strategy = AugmentCLIFetchStrategy()\n        let context = self.makeContext()\n        let error = SubprocessRunnerError.nonZeroExit(code: 1, stderr: \"crash\")\n        #expect(strategy.shouldFallback(on: error, context: context) == true)\n    }\n}\n\n#endif\n"
  },
  {
    "path": "Tests/CodexBarTests/AugmentStatusProbeTests.swift",
    "content": "import XCTest\n@testable import CodexBarCore\n\nfinal class AugmentStatusProbeTests: XCTestCase {\n    func test_debugRawProbe_returnsFormattedOutput() async {\n        // Given: A probe instance\n        let probe = AugmentStatusProbe()\n\n        // When: We call debugRawProbe\n        let output = await probe.debugRawProbe()\n\n        // Then: The output should contain expected debug information\n        XCTAssertTrue(output.contains(\"=== Augment Debug Probe @\"), \"Should contain debug header\")\n        XCTAssertTrue(\n            output.contains(\"Probe Success\") || output.contains(\"Probe Failed\"),\n            \"Should contain probe result status\")\n    }\n\n    func test_latestDumps_initiallyEmpty() async {\n        // Note: This test may fail if other tests have already run and captured dumps\n        // The ring buffer is shared across all tests in the process\n        // When: We request latest dumps\n        let dumps = await AugmentStatusProbe.latestDumps()\n\n        // Then: Should either be empty or contain previous test dumps\n        // We just verify it returns a non-empty string\n        XCTAssertFalse(dumps.isEmpty, \"Should return a string (either empty message or dumps)\")\n    }\n\n    func test_debugRawProbe_capturesFailureInDumps() async throws {\n        // Given: A probe with an invalid base URL that will fail\n        let invalidProbe = try AugmentStatusProbe(baseURL: XCTUnwrap(URL(string: \"https://invalid.example.com\")))\n\n        // When: We call debugRawProbe which should fail\n        let output = await invalidProbe.debugRawProbe()\n\n        // Then: The output should indicate failure\n        XCTAssertTrue(output.contains(\"Probe Failed\"), \"Should contain failure message\")\n\n        // And: The failure should be captured in dumps\n        let dumps = await AugmentStatusProbe.latestDumps()\n        XCTAssertNotEqual(dumps, \"No Augment probe dumps captured yet.\", \"Should have captured the failure\")\n        XCTAssertTrue(dumps.contains(\"Probe Failed\"), \"Dumps should contain the failure\")\n    }\n\n    func test_latestDumps_maintainsRingBuffer() async throws {\n        // Given: Multiple failed probes to fill the ring buffer\n        let invalidProbe = try AugmentStatusProbe(baseURL: XCTUnwrap(URL(string: \"https://invalid.example.com\")))\n\n        // When: We generate more than 5 dumps (the ring buffer size)\n        for _ in 1...7 {\n            _ = await invalidProbe.debugRawProbe()\n            // Small delay to ensure different timestamps\n            try await Task.sleep(nanoseconds: 100_000_000) // 0.1 seconds\n        }\n\n        // Then: The dumps should only contain the most recent 5\n        let dumps = await AugmentStatusProbe.latestDumps()\n        let separatorCount = dumps.components(separatedBy: \"\\n\\n---\\n\\n\").count\n        XCTAssertLessThanOrEqual(separatorCount, 5, \"Should maintain at most 5 dumps in ring buffer\")\n    }\n\n    func test_debugRawProbe_includesTimestamp() async {\n        // Given: A probe instance\n        let probe = AugmentStatusProbe()\n\n        // When: We call debugRawProbe\n        let output = await probe.debugRawProbe()\n\n        // Then: The output should include an ISO8601 timestamp\n        XCTAssertTrue(output.contains(\"@\"), \"Should contain timestamp marker\")\n        XCTAssertTrue(output.contains(\"===\"), \"Should contain debug header markers\")\n    }\n\n    func test_debugRawProbe_includesCreditsBalance() async {\n        // Given: A probe instance\n        let probe = AugmentStatusProbe()\n\n        // When: We call debugRawProbe\n        let output = await probe.debugRawProbe()\n\n        // Then: The output should mention credits balance (either in success or failure)\n        XCTAssertTrue(\n            output.contains(\"Credits Balance\") || output.contains(\"Probe Failed\"),\n            \"Should contain credits information or failure message\")\n    }\n\n    // MARK: - Cookie Domain Filtering Tests\n\n    func test_cookieDomainMatching_exactMatch() throws {\n        // Given: A session with a cookie that has exact domain match\n        let cookie = try XCTUnwrap(HTTPCookie(properties: [\n            .domain: \"app.augmentcode.com\",\n            .path: \"/\",\n            .name: \"session\",\n            .value: \"test123\",\n        ]))\n        let session = AugmentCookieImporter.SessionInfo(\n            cookies: [cookie],\n            sourceLabel: \"Test\")\n        let targetURL = try XCTUnwrap(URL(string: \"https://app.augmentcode.com/api/credits\"))\n\n        // When: We get the cookie header for the target URL\n        let cookieHeader = session.cookieHeader(for: targetURL)\n\n        // Then: It should include the cookie\n        XCTAssertEqual(cookieHeader, \"session=test123\", \"Cookie with exact domain should match\")\n    }\n\n    func test_cookieDomainMatching_parentDomain() throws {\n        // Given: A session with a cookie that has parent domain\n        let cookie = try XCTUnwrap(HTTPCookie(properties: [\n            .domain: \"augmentcode.com\",\n            .path: \"/\",\n            .name: \"session\",\n            .value: \"test123\",\n        ]))\n        let session = AugmentCookieImporter.SessionInfo(\n            cookies: [cookie],\n            sourceLabel: \"Test\")\n        let targetURL = try XCTUnwrap(URL(string: \"https://app.augmentcode.com/api/credits\"))\n\n        // When: We get the cookie header for the target URL\n        let cookieHeader = session.cookieHeader(for: targetURL)\n\n        // Then: It should include the cookie (parent domain matches subdomain)\n        XCTAssertEqual(cookieHeader, \"session=test123\", \"Cookie with parent domain should match subdomain\")\n    }\n\n    func test_cookieDomainMatching_wildcardDomain() throws {\n        // Given: A session with a cookie that has wildcard domain\n        let cookie = try XCTUnwrap(HTTPCookie(properties: [\n            .domain: \".augmentcode.com\",\n            .path: \"/\",\n            .name: \"session\",\n            .value: \"test123\",\n        ]))\n        let session = AugmentCookieImporter.SessionInfo(\n            cookies: [cookie],\n            sourceLabel: \"Test\")\n        let targetURL = try XCTUnwrap(URL(string: \"https://app.augmentcode.com/api/credits\"))\n\n        // When: We get the cookie header for the target URL\n        let cookieHeader = session.cookieHeader(for: targetURL)\n\n        // Then: It should include the cookie\n        XCTAssertEqual(cookieHeader, \"session=test123\", \"Cookie with wildcard domain should match\")\n    }\n\n    func test_cookieDomainMatching_wrongDomain() throws {\n        // Given: A session with a cookie from a different subdomain\n        let cookie = try XCTUnwrap(HTTPCookie(properties: [\n            .domain: \"auth.augmentcode.com\",\n            .path: \"/\",\n            .name: \"auth_token\",\n            .value: \"test123\",\n        ]))\n        let session = AugmentCookieImporter.SessionInfo(\n            cookies: [cookie],\n            sourceLabel: \"Test\")\n        let targetURL = try XCTUnwrap(URL(string: \"https://app.augmentcode.com/api/credits\"))\n\n        // When: We get the cookie header for the target URL\n        let cookieHeader = session.cookieHeader(for: targetURL)\n\n        // Then: It should NOT include the cookie\n        XCTAssertTrue(cookieHeader.isEmpty, \"Cookie from different subdomain should not match\")\n    }\n\n    func test_cookieDomainMatching_differentBaseDomain() throws {\n        // Given: A session with a cookie from a completely different domain\n        let cookie = try XCTUnwrap(HTTPCookie(properties: [\n            .domain: \"example.com\",\n            .path: \"/\",\n            .name: \"session\",\n            .value: \"test123\",\n        ]))\n        let session = AugmentCookieImporter.SessionInfo(\n            cookies: [cookie],\n            sourceLabel: \"Test\")\n        let targetURL = try XCTUnwrap(URL(string: \"https://app.augmentcode.com/api/credits\"))\n\n        // When: We get the cookie header for the target URL\n        let cookieHeader = session.cookieHeader(for: targetURL)\n\n        // Then: It should NOT include the cookie\n        XCTAssertTrue(cookieHeader.isEmpty, \"Cookie from different base domain should not match\")\n    }\n\n    func test_cookieHeader_filtersCorrectly() throws {\n        // Given: A session with multiple cookies from different domains\n        let cookies = try [\n            XCTUnwrap(HTTPCookie(properties: [\n                .domain: \"app.augmentcode.com\",\n                .path: \"/\",\n                .name: \"session\",\n                .value: \"valid1\",\n            ])),\n            XCTUnwrap(HTTPCookie(properties: [\n                .domain: \".augmentcode.com\",\n                .path: \"/\",\n                .name: \"_session\",\n                .value: \"valid2\",\n            ])),\n            XCTUnwrap(HTTPCookie(properties: [\n                .domain: \"auth.augmentcode.com\",\n                .path: \"/\",\n                .name: \"auth_token\",\n                .value: \"invalid1\",\n            ])),\n            XCTUnwrap(HTTPCookie(properties: [\n                .domain: \"billing.augmentcode.com\",\n                .path: \"/\",\n                .name: \"billing_session\",\n                .value: \"invalid2\",\n            ])),\n        ]\n\n        let session = AugmentCookieImporter.SessionInfo(\n            cookies: cookies,\n            sourceLabel: \"Test\")\n\n        let targetURL = try XCTUnwrap(URL(string: \"https://app.augmentcode.com/api/credits\"))\n\n        // When: We get the cookie header for the target URL\n        let cookieHeader = session.cookieHeader(for: targetURL)\n\n        // Then: It should only include cookies valid for app.augmentcode.com\n        XCTAssertTrue(cookieHeader.contains(\"session=valid1\"), \"Should include exact domain match\")\n        XCTAssertTrue(cookieHeader.contains(\"_session=valid2\"), \"Should include wildcard domain match\")\n        XCTAssertFalse(cookieHeader.contains(\"auth_token\"), \"Should NOT include auth subdomain cookie\")\n        XCTAssertFalse(cookieHeader.contains(\"billing_session\"), \"Should NOT include billing subdomain cookie\")\n    }\n\n    func test_cookieHeader_emptyWhenNoCookiesMatch() throws {\n        // Given: A session with cookies that don't match the target domain\n        let cookies = try [\n            XCTUnwrap(HTTPCookie(properties: [\n                .domain: \"auth.augmentcode.com\",\n                .path: \"/\",\n                .name: \"auth_token\",\n                .value: \"test\",\n            ])),\n            XCTUnwrap(HTTPCookie(properties: [\n                .domain: \"example.com\",\n                .path: \"/\",\n                .name: \"other\",\n                .value: \"test\",\n            ])),\n        ]\n\n        let session = AugmentCookieImporter.SessionInfo(\n            cookies: cookies,\n            sourceLabel: \"Test\")\n\n        let targetURL = try XCTUnwrap(URL(string: \"https://app.augmentcode.com/api/credits\"))\n\n        // When: We get the cookie header for the target URL\n        let cookieHeader = session.cookieHeader(for: targetURL)\n\n        // Then: It should be empty\n        XCTAssertTrue(cookieHeader.isEmpty, \"Should return empty string when no cookies match\")\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/BatteryDrainDiagnosticTests.swift",
    "content": "import AppKit\nimport CodexBarCore\nimport Foundation\nimport Testing\n@testable import CodexBar\n\n/// Regression coverage for battery drain caused by fallback-provider animation.\n/// See GitHub issues #269, #139.\n@MainActor\n@Suite(.serialized)\nstruct BatteryDrainDiagnosticTests {\n    private func ensureAppKitInitialized() {\n        _ = NSApplication.shared\n    }\n\n    private func makeStatusBarForTesting() -> NSStatusBar {\n        let env = ProcessInfo.processInfo.environment\n        if env[\"GITHUB_ACTIONS\"] == \"true\" || env[\"CI\"] == \"true\" {\n            return .system\n        }\n        return NSStatusBar()\n    }\n\n    @Test\n    func `Fallback provider should not animate when all providers are disabled`() {\n        self.ensureAppKitInitialized()\n\n        let settings = SettingsStore(\n            configStore: testConfigStore(suiteName: \"BatteryDrain-AllDisabled\"),\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        settings.statusChecksEnabled = false\n        settings.refreshFrequency = .manual\n        settings.mergeIcons = false\n\n        let registry = ProviderRegistry.shared\n        for provider in UsageProvider.allCases {\n            if let meta = registry.metadata[provider] {\n                settings.setProviderEnabled(provider: provider, metadata: meta, enabled: false)\n            }\n        }\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(\n            fetcher: fetcher,\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            settings: settings)\n\n        let controller = StatusItemController(\n            store: store,\n            settings: settings,\n            account: fetcher.loadAccountInfo(),\n            updater: DisabledUpdaterController(),\n            preferencesSelection: PreferencesSelection(),\n            statusBar: self.makeStatusBarForTesting())\n\n        #expect(\n            controller.needsMenuBarIconAnimation() == false,\n            \"Should not animate when only fallback provider is visible\")\n        #expect(\n            controller.animationDriver == nil,\n            \"Animation driver should not start for fallback provider\")\n    }\n\n    @Test\n    func `Enabled provider with data should not animate`() {\n        self.ensureAppKitInitialized()\n\n        let settings = SettingsStore(\n            configStore: testConfigStore(suiteName: \"BatteryDrain-HasData\"),\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        settings.statusChecksEnabled = false\n        settings.refreshFrequency = .manual\n        settings.mergeIcons = true\n        settings.selectedMenuProvider = .codex\n\n        let registry = ProviderRegistry.shared\n        if let meta = registry.metadata[.codex] {\n            settings.setProviderEnabled(provider: .codex, metadata: meta, enabled: true)\n        }\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(\n            fetcher: fetcher,\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            settings: settings)\n\n        let snapshot = UsageSnapshot(\n            primary: RateWindow(usedPercent: 50, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            secondary: RateWindow(usedPercent: 30, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            updatedAt: Date())\n        store._setSnapshotForTesting(snapshot, provider: .codex)\n\n        let controller = StatusItemController(\n            store: store,\n            settings: settings,\n            account: fetcher.loadAccountInfo(),\n            updater: DisabledUpdaterController(),\n            preferencesSelection: PreferencesSelection(),\n            statusBar: self.makeStatusBarForTesting())\n\n        #expect(\n            controller.needsMenuBarIconAnimation() == false,\n            \"Should not animate when provider has data\")\n        #expect(\n            controller.animationDriver == nil,\n            \"Animation driver should be nil when data is present\")\n    }\n\n    @Test\n    func `Enabled provider without data should animate`() {\n        self.ensureAppKitInitialized()\n\n        let settings = SettingsStore(\n            configStore: testConfigStore(suiteName: \"BatteryDrain-NoData\"),\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        settings.statusChecksEnabled = false\n        settings.refreshFrequency = .manual\n        settings.mergeIcons = false\n\n        let registry = ProviderRegistry.shared\n        if let meta = registry.metadata[.codex] {\n            settings.setProviderEnabled(provider: .codex, metadata: meta, enabled: true)\n        }\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(\n            fetcher: fetcher,\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            settings: settings)\n\n        let controller = StatusItemController(\n            store: store,\n            settings: settings,\n            account: fetcher.loadAccountInfo(),\n            updater: DisabledUpdaterController(),\n            preferencesSelection: PreferencesSelection(),\n            statusBar: self.makeStatusBarForTesting())\n\n        #expect(\n            controller.needsMenuBarIconAnimation() == true,\n            \"Should animate when enabled provider has no data\")\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/BrowserCookieOrderLabelTests.swift",
    "content": "import CodexBarCore\nimport SweetCookieKit\nimport Testing\n\nstruct BrowserCookieOrderStatusStringTests {\n    #if os(macOS)\n    @Test\n    func `cursor no session includes browser login hint`() {\n        let order = ProviderDefaults.metadata[.cursor]?.browserCookieOrder ?? Browser.defaultImportOrder\n        let message = CursorStatusProbeError.noSessionCookie.errorDescription ?? \"\"\n        #expect(message.contains(order.loginHint))\n    }\n\n    @Test\n    func `factory no session includes browser login hint`() {\n        let order = ProviderDefaults.metadata[.factory]?.browserCookieOrder ?? Browser.defaultImportOrder\n        let message = FactoryStatusProbeError.noSessionCookie.errorDescription ?? \"\"\n        #expect(message.contains(order.loginHint))\n    }\n    #endif\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/BrowserDetectionTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n\n#if os(macOS)\nimport SweetCookieKit\n\nstruct BrowserDetectionTests {\n    @Test\n    func `safari always installed`() {\n        #expect(BrowserDetection(cacheTTL: 0).isAppInstalled(.safari) == true)\n        #expect(BrowserDetection(cacheTTL: 0).isCookieSourceAvailable(.safari) == true)\n    }\n\n    @Test\n    func `filter installed includes safari`() {\n        let detection = BrowserDetection(cacheTTL: 0)\n        let browsers: [Browser] = [.safari, .chrome, .firefox]\n        #expect(browsers.cookieImportCandidates(using: detection).contains(.safari))\n    }\n\n    @Test\n    func `filter preserves order`() {\n        BrowserCookieAccessGate.resetForTesting()\n\n        let temp = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)\n        try? FileManager.default.createDirectory(at: temp, withIntermediateDirectories: true)\n        defer { try? FileManager.default.removeItem(at: temp) }\n\n        let firefoxProfile = temp\n            .appendingPathComponent(\"Library\")\n            .appendingPathComponent(\"Application Support\")\n            .appendingPathComponent(\"Firefox\")\n            .appendingPathComponent(\"Profiles\")\n            .appendingPathComponent(\"abc.default-release\")\n        try? FileManager.default.createDirectory(at: firefoxProfile, withIntermediateDirectories: true)\n        FileManager.default.createFile(\n            atPath: firefoxProfile.appendingPathComponent(\"cookies.sqlite\").path,\n            contents: Data())\n\n        let detection = BrowserDetection(homeDirectory: temp.path, cacheTTL: 0)\n        let browsers: [Browser] = [.firefox, .safari, .chrome]\n        // Chrome is filtered out deterministically because it lacks usable on-disk profile/cookie store data.\n        #expect(browsers.cookieImportCandidates(using: detection) == [.firefox, .safari])\n    }\n\n    @Test\n    func `chrome requires profile data`() throws {\n        let temp = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)\n        try FileManager.default.createDirectory(at: temp, withIntermediateDirectories: true)\n        defer { try? FileManager.default.removeItem(at: temp) }\n\n        let detection = BrowserDetection(homeDirectory: temp.path, cacheTTL: 0)\n        #expect(detection.isCookieSourceAvailable(.chrome) == false)\n\n        let profile = temp\n            .appendingPathComponent(\"Library\")\n            .appendingPathComponent(\"Application Support\")\n            .appendingPathComponent(\"Google\")\n            .appendingPathComponent(\"Chrome\")\n            .appendingPathComponent(\"Default\")\n        try FileManager.default.createDirectory(at: profile, withIntermediateDirectories: true)\n        let cookiesDir = profile.appendingPathComponent(\"Network\")\n        try FileManager.default.createDirectory(at: cookiesDir, withIntermediateDirectories: true)\n        FileManager.default.createFile(atPath: cookiesDir.appendingPathComponent(\"Cookies\").path, contents: Data())\n\n        #expect(detection.isCookieSourceAvailable(.chrome) == true)\n    }\n\n    @Test\n    func `dia requires profile data`() throws {\n        let temp = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)\n        try FileManager.default.createDirectory(at: temp, withIntermediateDirectories: true)\n        defer { try? FileManager.default.removeItem(at: temp) }\n\n        let detection = BrowserDetection(homeDirectory: temp.path, cacheTTL: 0)\n        #expect(detection.isCookieSourceAvailable(.dia) == false)\n\n        let profile = temp\n            .appendingPathComponent(\"Library\")\n            .appendingPathComponent(\"Application Support\")\n            .appendingPathComponent(\"Dia\")\n            .appendingPathComponent(\"User Data\")\n            .appendingPathComponent(\"Default\")\n        try FileManager.default.createDirectory(at: profile, withIntermediateDirectories: true)\n        let cookiesDir = profile.appendingPathComponent(\"Network\")\n        try FileManager.default.createDirectory(at: cookiesDir, withIntermediateDirectories: true)\n        FileManager.default.createFile(atPath: cookiesDir.appendingPathComponent(\"Cookies\").path, contents: Data())\n\n        #expect(detection.isCookieSourceAvailable(.dia) == true)\n    }\n\n    @Test\n    func `firefox requires default profile dir`() throws {\n        let temp = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)\n        try FileManager.default.createDirectory(at: temp, withIntermediateDirectories: true)\n        defer { try? FileManager.default.removeItem(at: temp) }\n\n        let profiles = temp\n            .appendingPathComponent(\"Library\")\n            .appendingPathComponent(\"Application Support\")\n            .appendingPathComponent(\"Firefox\")\n            .appendingPathComponent(\"Profiles\")\n        try FileManager.default.createDirectory(at: profiles, withIntermediateDirectories: true)\n\n        let detection = BrowserDetection(homeDirectory: temp.path, cacheTTL: 0)\n        #expect(detection.isCookieSourceAvailable(.firefox) == false)\n\n        let profile = profiles.appendingPathComponent(\"abc.default-release\")\n        try FileManager.default.createDirectory(at: profile, withIntermediateDirectories: true)\n        FileManager.default.createFile(atPath: profile.appendingPathComponent(\"cookies.sqlite\").path, contents: Data())\n        #expect(detection.isCookieSourceAvailable(.firefox) == true)\n    }\n\n    @Test\n    func `zen accepts uppercase default profile dir`() throws {\n        let temp = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)\n        try FileManager.default.createDirectory(at: temp, withIntermediateDirectories: true)\n        defer { try? FileManager.default.removeItem(at: temp) }\n\n        let profiles = temp\n            .appendingPathComponent(\"Library\")\n            .appendingPathComponent(\"Application Support\")\n            .appendingPathComponent(\"zen\")\n            .appendingPathComponent(\"Profiles\")\n        try FileManager.default.createDirectory(at: profiles, withIntermediateDirectories: true)\n\n        let detection = BrowserDetection(homeDirectory: temp.path, cacheTTL: 0)\n        #expect(detection.isCookieSourceAvailable(.zen) == false)\n\n        let profile = profiles.appendingPathComponent(\"abc.Default (release)\")\n        try FileManager.default.createDirectory(at: profile, withIntermediateDirectories: true)\n        FileManager.default.createFile(atPath: profile.appendingPathComponent(\"cookies.sqlite\").path, contents: Data())\n        #expect(detection.isCookieSourceAvailable(.zen) == true)\n    }\n}\n\n#else\n\nstruct BrowserDetectionTests {\n    @Test\n    func `non mac OS returns no browsers`() {\n        #expect(BrowserDetection(cacheTTL: 0).isCookieSourceAvailable(Browser()) == false)\n    }\n\n    @Test\n    func `non mac OS filter returns empty`() {\n        let detection = BrowserDetection(cacheTTL: 0)\n        let browsers = [Browser(), Browser()]\n        #expect(browsers.cookieImportCandidates(using: detection).isEmpty == true)\n    }\n}\n\n#endif\n"
  },
  {
    "path": "Tests/CodexBarTests/CLIArgumentParsingTests.swift",
    "content": "import CodexBarCore\nimport Commander\nimport Testing\n@testable import CodexBarCLI\n\nstruct CLIArgumentParsingTests {\n    @Test\n    func `json shortcut does not enable json logs`() throws {\n        let signature = CodexBarCLI._usageSignatureForTesting()\n        let parser = CommandParser(signature: signature)\n        let parsed = try parser.parse(arguments: [\"--json\"])\n\n        #expect(parsed.flags.contains(\"jsonShortcut\"))\n        #expect(!parsed.flags.contains(\"jsonOutput\"))\n        #expect(CodexBarCLI._decodeFormatForTesting(from: parsed) == .json)\n    }\n\n    @Test\n    func `json output flag enables json logs`() throws {\n        let signature = CodexBarCLI._usageSignatureForTesting()\n        let parser = CommandParser(signature: signature)\n        let parsed = try parser.parse(arguments: [\"--json-output\"])\n\n        #expect(parsed.flags.contains(\"jsonOutput\"))\n        #expect(!parsed.flags.contains(\"jsonShortcut\"))\n        #expect(CodexBarCLI._decodeFormatForTesting(from: parsed) == .text)\n    }\n\n    @Test\n    func `log level and verbose are parsed`() throws {\n        let signature = CodexBarCLI._usageSignatureForTesting()\n        let parser = CommandParser(signature: signature)\n        let parsed = try parser.parse(arguments: [\"--log-level\", \"info\", \"--verbose\"])\n\n        #expect(parsed.flags.contains(\"verbose\"))\n        #expect(parsed.options[\"logLevel\"] == [\"info\"])\n    }\n\n    @Test\n    func `resolved log level defaults to error`() {\n        #expect(CodexBarCLI.resolvedLogLevel(verbose: false, rawLevel: nil) == .error)\n        #expect(CodexBarCLI.resolvedLogLevel(verbose: true, rawLevel: nil) == .debug)\n        #expect(CodexBarCLI.resolvedLogLevel(verbose: false, rawLevel: \"info\") == .info)\n    }\n\n    @Test\n    func `format option overrides json shortcut`() throws {\n        let signature = CodexBarCLI._usageSignatureForTesting()\n        let parser = CommandParser(signature: signature)\n        let parsed = try parser.parse(arguments: [\"--json\", \"--format\", \"text\"])\n\n        #expect(parsed.flags.contains(\"jsonShortcut\"))\n        #expect(parsed.options[\"format\"] == [\"text\"])\n        #expect(CodexBarCLI._decodeFormatForTesting(from: parsed) == .text)\n    }\n\n    @Test\n    func `json only enables json format`() throws {\n        let signature = CodexBarCLI._usageSignatureForTesting()\n        let parser = CommandParser(signature: signature)\n        let parsed = try parser.parse(arguments: [\"--json-only\"])\n\n        #expect(parsed.flags.contains(\"jsonOnly\"))\n        #expect(!parsed.flags.contains(\"jsonOutput\"))\n        #expect(CodexBarCLI._decodeFormatForTesting(from: parsed) == .json)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/CLICostTests.swift",
    "content": "import CodexBarCore\nimport Commander\nimport Foundation\nimport Testing\n@testable import CodexBarCLI\n\nstruct CLICostTests {\n    @Test\n    func `cost json shortcut does not enable json logs`() throws {\n        let signature = CodexBarCLI._costSignatureForTesting()\n        let parser = CommandParser(signature: signature)\n        let parsed = try parser.parse(arguments: [\"--json\"])\n\n        #expect(parsed.flags.contains(\"jsonShortcut\"))\n        #expect(!parsed.flags.contains(\"jsonOutput\"))\n        #expect(CodexBarCLI._decodeFormatForTesting(from: parsed) == .json)\n    }\n\n    @Test\n    func `renders cost text snapshot`() {\n        let snap = CostUsageTokenSnapshot(\n            sessionTokens: 1200,\n            sessionCostUSD: 1.25,\n            last30DaysTokens: 9000,\n            last30DaysCostUSD: 9.99,\n            daily: [],\n            updatedAt: Date(timeIntervalSince1970: 0))\n\n        let output = CodexBarCLI.renderCostText(provider: .claude, snapshot: snap, useColor: false)\n            .replacingOccurrences(of: \"\\u{00A0}\", with: \" \")\n            .replacingOccurrences(of: \"$ \", with: \"$\")\n\n        #expect(output.contains(\"Claude Cost (local)\"))\n        #expect(output.contains(\"Today: $1.25 · 1.2K tokens\"))\n        #expect(output.contains(\"Last 30 days: $9.99 · 9K tokens\"))\n    }\n\n    @Test\n    func `encodes cost payload JSON`() throws {\n        let payload = CostPayload(\n            provider: \"claude\",\n            source: \"local\",\n            updatedAt: Date(timeIntervalSince1970: 1_700_000_000),\n            sessionTokens: 100,\n            sessionCostUSD: 0.5,\n            last30DaysTokens: 200,\n            last30DaysCostUSD: 1.5,\n            daily: [\n                CostDailyEntryPayload(\n                    date: \"2025-12-20\",\n                    inputTokens: 10,\n                    outputTokens: 5,\n                    cacheReadTokens: 2,\n                    cacheCreationTokens: 3,\n                    totalTokens: 15,\n                    costUSD: 0.01,\n                    modelsUsed: [\"claude-sonnet-4-20250514\"],\n                    modelBreakdowns: [\n                        CostModelBreakdownPayload(\n                            modelName: \"claude-sonnet-4-20250514\",\n                            costUSD: 0.01,\n                            totalTokens: 15),\n                    ]),\n            ],\n            totals: CostTotalsPayload(\n                totalInputTokens: 10,\n                totalOutputTokens: 5,\n                cacheReadTokens: 2,\n                cacheCreationTokens: 3,\n                totalTokens: 15,\n                totalCostUSD: 0.01),\n            error: nil)\n\n        let encoder = JSONEncoder()\n        encoder.dateEncodingStrategy = .secondsSince1970\n        let data = try encoder.encode(payload)\n        guard let json = String(data: data, encoding: .utf8) else {\n            Issue.record(\"Failed to decode cost payload JSON\")\n            return\n        }\n\n        #expect(json.contains(\"\\\"provider\\\":\\\"claude\\\"\"))\n        #expect(json.contains(\"\\\"source\\\":\\\"local\\\"\"))\n        #expect(json.contains(\"\\\"daily\\\"\"))\n        #expect(json.contains(\"\\\"totals\\\"\"))\n        #expect(json.contains(\"\\\"cacheReadTokens\\\":2\"))\n        #expect(json.contains(\"\\\"cacheCreationTokens\\\":3\"))\n        #expect(json.contains(\"\\\"totalCost\\\"\"))\n        #expect(json.contains(\"\\\"totalTokens\\\":15\"))\n        #expect(json.contains(\"1700000000\"))\n    }\n\n    @Test\n    func `encodes exact codex model I ds and zero cost breakdowns`() throws {\n        let payload = CostPayload(\n            provider: \"codex\",\n            source: \"local\",\n            updatedAt: Date(timeIntervalSince1970: 1_700_000_000),\n            sessionTokens: 155,\n            sessionCostUSD: 0,\n            last30DaysTokens: 155,\n            last30DaysCostUSD: 0,\n            daily: [\n                CostDailyEntryPayload(\n                    date: \"2025-12-21\",\n                    inputTokens: 120,\n                    outputTokens: 15,\n                    cacheReadTokens: 20,\n                    cacheCreationTokens: nil,\n                    totalTokens: 155,\n                    costUSD: 0,\n                    modelsUsed: [\"gpt-5.3-codex-spark\", \"gpt-5.2-codex\"],\n                    modelBreakdowns: [\n                        CostModelBreakdownPayload(modelName: \"gpt-5.3-codex-spark\", costUSD: 0, totalTokens: 15),\n                        CostModelBreakdownPayload(modelName: \"gpt-5.2-codex\", costUSD: 1.23, totalTokens: 140),\n                    ]),\n            ],\n            totals: CostTotalsPayload(\n                totalInputTokens: 120,\n                totalOutputTokens: 15,\n                cacheReadTokens: 20,\n                cacheCreationTokens: nil,\n                totalTokens: 155,\n                totalCostUSD: 0),\n            error: nil)\n\n        let encoder = JSONEncoder()\n        encoder.dateEncodingStrategy = .secondsSince1970\n        let data = try encoder.encode(payload)\n        guard let json = String(data: data, encoding: .utf8) else {\n            Issue.record(\"Failed to decode cost payload JSON\")\n            return\n        }\n\n        #expect(json.contains(\"\\\"gpt-5.3-codex-spark\\\"\"))\n        #expect(json.contains(\"\\\"gpt-5.2-codex\\\"\"))\n        #expect(!json.contains(\"\\\"gpt-5.2\\\"\"))\n        #expect(json.contains(\"\\\"cost\\\":0\"))\n        #expect(json.contains(\"\\\"totalTokens\\\":140\"))\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/CLIEntryTests.swift",
    "content": "import CodexBarCore\nimport Commander\nimport Foundation\nimport Testing\n@testable import CodexBarCLI\n\nstruct CLIEntryTests {\n    @Test\n    func `effective argv defaults to usage`() {\n        #expect(CodexBarCLI.effectiveArgv([]) == [\"usage\"])\n        #expect(CodexBarCLI.effectiveArgv([\"--json\"]) == [\"usage\", \"--json\"])\n        #expect(CodexBarCLI.effectiveArgv([\"usage\", \"--json\"]) == [\"usage\", \"--json\"])\n    }\n\n    @Test\n    func `decodes format from options and flags`() {\n        let jsonOption = ParsedValues(positional: [], options: [\"format\": [\"json\"]], flags: [])\n        #expect(CodexBarCLI._decodeFormatForTesting(from: jsonOption) == .json)\n\n        let jsonFlag = ParsedValues(positional: [], options: [:], flags: [\"json\"])\n        #expect(CodexBarCLI._decodeFormatForTesting(from: jsonFlag) == .json)\n\n        let textDefault = ParsedValues(positional: [], options: [:], flags: [])\n        #expect(CodexBarCLI._decodeFormatForTesting(from: textDefault) == .text)\n    }\n\n    @Test\n    func `provider selection prefers override`() {\n        let selection = CodexBarCLI.providerSelection(rawOverride: \"codex\", enabled: [.claude, .gemini])\n        #expect(selection.asList == [.codex])\n    }\n\n    @Test\n    func `normalize version extracts numeric`() {\n        #expect(CodexBarCLI.normalizeVersion(raw: \"codex 1.2.3 (build 4)\") == \"1.2.3\")\n        #expect(CodexBarCLI.normalizeVersion(raw: \"  v2.0  \") == \"2.0\")\n    }\n\n    @Test\n    func `make header includes version when available`() {\n        let header = CodexBarCLI.makeHeader(provider: .codex, version: \"1.2.3\", source: \"cli\")\n        #expect(header.contains(\"Codex\"))\n        #expect(header.contains(\"1.2.3\"))\n        #expect(header.contains(\"cli\"))\n    }\n\n    @Test\n    func `render open AI web dashboard text includes summary`() {\n        let event = CreditEvent(\n            date: Date(timeIntervalSince1970: 1_700_000_000),\n            service: \"codex\",\n            creditsUsed: 10)\n        let snapshot = OpenAIDashboardSnapshot(\n            signedInEmail: \"user@example.com\",\n            codeReviewRemainingPercent: 45,\n            creditEvents: [event],\n            dailyBreakdown: [],\n            usageBreakdown: [],\n            creditsPurchaseURL: nil,\n            updatedAt: Date())\n\n        let text = CodexBarCLI.renderOpenAIWebDashboardText(snapshot)\n\n        #expect(text.contains(\"Web session: user@example.com\"))\n        #expect(text.contains(\"Code review: 45% remaining\"))\n        #expect(text.contains(\"Web history: 1 events\"))\n    }\n\n    @Test\n    func `maps errors to exit codes`() {\n        #expect(CodexBarCLI.mapError(CodexStatusProbeError.codexNotInstalled) == ExitCode(2))\n        #expect(CodexBarCLI.mapError(CodexStatusProbeError.timedOut) == ExitCode(4))\n        #expect(CodexBarCLI.mapError(UsageError.noRateLimitsFound) == ExitCode(3))\n    }\n\n    @Test\n    func `provider selection falls back to both for primary pair`() {\n        let selection = CodexBarCLI.providerSelection(rawOverride: nil, enabled: [.codex, .claude])\n        switch selection {\n        case .both:\n            break\n        default:\n            #expect(Bool(false))\n        }\n    }\n\n    @Test\n    func `provider selection falls back to custom when non primary`() {\n        let selection = CodexBarCLI.providerSelection(rawOverride: nil, enabled: [.codex, .gemini])\n        switch selection {\n        case let .custom(providers):\n            #expect(providers == [.codex, .gemini])\n        default:\n            #expect(Bool(false))\n        }\n    }\n\n    @Test\n    func `provider selection defaults to codex when empty`() {\n        let selection = CodexBarCLI.providerSelection(rawOverride: nil, enabled: [])\n        switch selection {\n        case let .single(provider):\n            #expect(provider == .codex)\n        default:\n            #expect(Bool(false))\n        }\n    }\n\n    @Test\n    func `decodes source and timeout options`() throws {\n        let signature = CodexBarCLI._usageSignatureForTesting()\n        let parser = CommandParser(signature: signature)\n        let parsed = try parser.parse(arguments: [\"--web-timeout\", \"45\", \"--source\", \"oauth\"])\n        #expect(CodexBarCLI._decodeWebTimeoutForTesting(from: parsed) == 45)\n        #expect(CodexBarCLI._decodeSourceModeForTesting(from: parsed) == .oauth)\n\n        let parsedWeb = try parser.parse(arguments: [\"--web\"])\n        #expect(CodexBarCLI._decodeSourceModeForTesting(from: parsedWeb) == .web)\n    }\n\n    @Test\n    func `should use color respects format and flags`() {\n        #expect(!CodexBarCLI.shouldUseColor(noColor: true, format: .text))\n        #expect(!CodexBarCLI.shouldUseColor(noColor: false, format: .json))\n    }\n\n    @Test\n    func `kilo usage text notes show fallback only for auto resolved to CLI`() {\n        #expect(CodexBarCLI.usageTextNotes(\n            provider: .kilo,\n            sourceMode: .auto,\n            resolvedSourceLabel: \"cli\") == [\"Using CLI fallback\"])\n        #expect(CodexBarCLI.usageTextNotes(\n            provider: .kilo,\n            sourceMode: .api,\n            resolvedSourceLabel: \"cli\").isEmpty)\n        #expect(CodexBarCLI.usageTextNotes(\n            provider: .codex,\n            sourceMode: .auto,\n            resolvedSourceLabel: \"cli\").isEmpty)\n    }\n\n    @Test\n    func `kilo auto fallback summary includes ordered attempt details`() {\n        let attempts = [\n            ProviderFetchAttempt(\n                strategyID: \"kilo.api\",\n                kind: .apiToken,\n                wasAvailable: true,\n                errorDescription: \"Kilo authentication failed (401/403).\"),\n            ProviderFetchAttempt(\n                strategyID: \"kilo.cli\",\n                kind: .cli,\n                wasAvailable: true,\n                errorDescription: \"Kilo CLI session not found.\"),\n        ]\n\n        let summary = CodexBarCLI.kiloAutoFallbackSummary(\n            provider: .kilo,\n            sourceMode: .auto,\n            attempts: attempts)\n        let expected = [\n            \"Kilo auto fallback attempts: api: Kilo authentication failed (401/403).\",\n            \" -> cli: Kilo CLI session not found.\",\n        ].joined()\n\n        #expect(\n            summary ==\n                expected)\n    }\n\n    @Test\n    func `kilo auto fallback summary is nil outside kilo auto failures`() {\n        let attempts = [\n            ProviderFetchAttempt(\n                strategyID: \"kilo.api\",\n                kind: .apiToken,\n                wasAvailable: true,\n                errorDescription: \"example\"),\n        ]\n\n        #expect(CodexBarCLI.kiloAutoFallbackSummary(\n            provider: .kilo,\n            sourceMode: .api,\n            attempts: attempts) == nil)\n        #expect(CodexBarCLI.kiloAutoFallbackSummary(\n            provider: .codex,\n            sourceMode: .auto,\n            attempts: attempts) == nil)\n    }\n\n    @Test\n    func `source mode requires web support is provider aware`() {\n        #expect(CodexBarCLI.sourceModeRequiresWebSupport(.web, provider: .kilo))\n        #expect(CodexBarCLI.sourceModeRequiresWebSupport(.auto, provider: .codex))\n        #expect(!CodexBarCLI.sourceModeRequiresWebSupport(.auto, provider: .kilo))\n        #expect(!CodexBarCLI.sourceModeRequiresWebSupport(.api, provider: .kilo))\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/CLIOutputTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCLI\n\nstruct CLIOutputTests {\n    @Test\n    func `output preferences json only forces JSON`() {\n        let output = CLIOutputPreferences.from(argv: [\"--json-only\"])\n        #expect(output.jsonOnly == true)\n        #expect(output.format == .json)\n    }\n\n    @Test\n    func `cli error payload is JSON array`() throws {\n        let payload = CodexBarCLI.makeCLIErrorPayload(\n            message: \"Nope\",\n            code: .failure,\n            kind: .args,\n            pretty: false)\n        #expect(payload != nil)\n        let data = payload?.data(using: .utf8) ?? Data()\n        let json = try JSONSerialization.jsonObject(with: data) as? [Any]\n        #expect(json?.isEmpty == false)\n        let first = json?.first as? [String: Any]\n        #expect(first?[\"provider\"] as? String == \"cli\")\n        let error = first?[\"error\"] as? [String: Any]\n        #expect(error?[\"message\"] as? String == \"Nope\")\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/CLIProviderSelectionTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n@testable import CodexBarCLI\n\nstruct CLIProviderSelectionTests {\n    @Test\n    func `help includes gemini and all`() {\n        let usage = CodexBarCLI.usageHelp(version: \"0.0.0\")\n        let root = CodexBarCLI.rootHelp(version: \"0.0.0\")\n        let expectedProviders = [\n            \"--provider codex|\",\n            \"|claude|\",\n            \"|factory|\",\n            \"|zai|\",\n            \"|cursor|\",\n            \"|gemini|\",\n            \"|antigravity|\",\n            \"|copilot|\",\n            \"|synthetic|\",\n            \"|kiro|\",\n            \"|warp|\",\n            \"|ollama|\",\n            \"|both|\",\n            \"|all]\",\n        ]\n        for provider in expectedProviders {\n            #expect(usage.contains(provider))\n            #expect(root.contains(provider))\n        }\n        #expect(usage.contains(\"--json\"))\n        #expect(root.contains(\"--json\"))\n        #expect(usage.contains(\"--json-only\"))\n        #expect(root.contains(\"--json-only\"))\n        #expect(usage.contains(\"--json-output\"))\n        #expect(root.contains(\"--json-output\"))\n        #expect(usage.contains(\"--log-level\"))\n        #expect(root.contains(\"--log-level\"))\n        #expect(usage.contains(\"--verbose\"))\n        #expect(root.contains(\"--verbose\"))\n        #expect(usage.contains(\"codexbar usage --provider gemini\"))\n        #expect(usage.contains(\"codexbar usage --format json --provider all --pretty\"))\n        #expect(root.contains(\"codexbar --provider gemini\"))\n    }\n\n    @Test\n    func `help mentions source flag`() {\n        let usage = CodexBarCLI.usageHelp(version: \"0.0.0\")\n        let root = CodexBarCLI.rootHelp(version: \"0.0.0\")\n\n        func tokens(_ text: String) -> [String] {\n            let split = CharacterSet.whitespacesAndNewlines.union(CharacterSet(charactersIn: \"[]|,\"))\n            return text.components(separatedBy: split).filter { !$0.isEmpty }\n        }\n\n        #expect(usage.contains(\"--source\"))\n        #expect(root.contains(\"--source\"))\n        #expect(usage.contains(\"--web-timeout\"))\n        #expect(usage.contains(\"--web-debug-dump-html\"))\n        #expect(!tokens(usage).contains(\"--web\"))\n        #expect(!tokens(root).contains(\"--web\"))\n        #expect(!tokens(usage).contains(\"--claude-source\"))\n        #expect(!tokens(root).contains(\"--claude-source\"))\n    }\n\n    @Test\n    func `provider selection respects override`() {\n        let selection = CodexBarCLI.providerSelection(rawOverride: \"gemini\", enabled: [.codex, .claude])\n        #expect(selection.asList == [.gemini])\n    }\n\n    @Test\n    func `provider selection uses all when enabled`() {\n        let selection = CodexBarCLI.providerSelection(\n            rawOverride: nil,\n            enabled: [.codex, .claude, .zai, .cursor, .gemini, .antigravity, .factory, .copilot])\n        #expect(selection.asList == ProviderSelection.all.asList)\n    }\n\n    @Test\n    func `provider selection uses both for codex and claude`() {\n        let selection = CodexBarCLI.providerSelection(rawOverride: nil, enabled: [.codex, .claude])\n        #expect(selection.asList == [.codex, .claude])\n    }\n\n    @Test\n    func `provider selection uses custom for codex and gemini`() {\n        let enabled: [UsageProvider] = [.codex, .gemini]\n        let selection = CodexBarCLI.providerSelection(rawOverride: nil, enabled: enabled)\n        #expect(selection.asList == enabled)\n    }\n\n    @Test\n    func `provider selection accepts kiro alias`() {\n        let selection = CodexBarCLI.providerSelection(rawOverride: \"kiro-cli\", enabled: [.codex])\n        #expect(selection.asList == [.kiro])\n    }\n\n    @Test\n    func `provider selection defaults to codex when empty`() {\n        let selection = CodexBarCLI.providerSelection(rawOverride: nil, enabled: [])\n        #expect(selection.asList == [.codex])\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/CLISnapshotTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n@testable import CodexBarCLI\n\nstruct CLISnapshotTests {\n    @Test\n    func `renders text snapshot for codex`() {\n        let identity = ProviderIdentitySnapshot(\n            providerID: .codex,\n            accountEmail: \"user@example.com\",\n            accountOrganization: nil,\n            loginMethod: \"pro\")\n        let snap = UsageSnapshot(\n            primary: .init(usedPercent: 12, windowMinutes: 300, resetsAt: nil, resetDescription: \"today at 3:00 PM\"),\n            secondary: .init(usedPercent: 25, windowMinutes: 10080, resetsAt: nil, resetDescription: \"Fri at 9:00 AM\"),\n            tertiary: nil,\n            updatedAt: Date(timeIntervalSince1970: 0),\n            identity: identity)\n\n        let output = CLIRenderer.renderText(\n            provider: .codex,\n            snapshot: snap,\n            credits: CreditsSnapshot(remaining: 42, events: [], updatedAt: Date()),\n            context: RenderContext(\n                header: \"Codex 1.2.3 (codex-cli)\",\n                status: ProviderStatusPayload(\n                    indicator: .minor,\n                    description: \"Degraded performance\",\n                    updatedAt: Date(timeIntervalSince1970: 0),\n                    url: \"https://status.example.com\"),\n                useColor: false,\n                resetStyle: .absolute))\n\n        #expect(output.contains(\"Codex 1.2.3 (codex-cli)\"))\n        #expect(output.contains(\"Status: Partial outage – Degraded performance\"))\n        #expect(output.contains(\"Codex\"))\n        #expect(output.contains(\"Session: 88% left\"))\n        #expect(output.contains(\"Weekly: 75% left\"))\n        #expect(output.contains(\"Credits: 42\"))\n        #expect(output.contains(\"Account: user@example.com\"))\n        #expect(output.contains(\"Plan: Pro\"))\n    }\n\n    @Test\n    func `renders text snapshot for claude without weekly`() {\n        let snap = UsageSnapshot(\n            primary: .init(usedPercent: 2, windowMinutes: nil, resetsAt: nil, resetDescription: \"3pm (Europe/Vienna)\"),\n            secondary: nil,\n            tertiary: nil,\n            updatedAt: Date(timeIntervalSince1970: 0))\n\n        let output = CLIRenderer.renderText(\n            provider: .claude,\n            snapshot: snap,\n            credits: nil,\n            context: RenderContext(\n                header: \"Claude Code 2.0.69 (claude)\",\n                status: nil,\n                useColor: false,\n                resetStyle: .absolute))\n\n        #expect(output.contains(\"Session: 98% left\"))\n        #expect(!output.contains(\"Weekly:\"))\n    }\n\n    @Test\n    func `renders warp unlimited as detail not reset`() {\n        let meta = ProviderDescriptorRegistry.descriptor(for: .warp).metadata\n        let snap = UsageSnapshot(\n            primary: .init(usedPercent: 0, windowMinutes: nil, resetsAt: nil, resetDescription: \"Unlimited\"),\n            secondary: nil,\n            tertiary: nil,\n            updatedAt: Date(timeIntervalSince1970: 0),\n            identity: ProviderIdentitySnapshot(\n                providerID: .warp,\n                accountEmail: nil,\n                accountOrganization: nil,\n                loginMethod: nil))\n\n        let output = CLIRenderer.renderText(\n            provider: .warp,\n            snapshot: snap,\n            credits: nil,\n            context: RenderContext(\n                header: \"Warp 0.0.0 (warp)\",\n                status: nil,\n                useColor: false,\n                resetStyle: .absolute))\n\n        #expect(output.contains(\"\\(meta.sessionLabel): 100% left\"))\n        #expect(!output.contains(\"Resets Unlimited\"))\n        #expect(output.contains(\"Unlimited\"))\n    }\n\n    @Test\n    func `renders warp credits as detail and reset as date`() {\n        let meta = ProviderDescriptorRegistry.descriptor(for: .warp).metadata\n        let now = Date(timeIntervalSince1970: 0)\n        let snap = UsageSnapshot(\n            primary: .init(\n                usedPercent: 10,\n                windowMinutes: nil,\n                resetsAt: now.addingTimeInterval(3600),\n                resetDescription: \"10/100 credits\"),\n            secondary: nil,\n            tertiary: nil,\n            updatedAt: now,\n            identity: ProviderIdentitySnapshot(\n                providerID: .warp,\n                accountEmail: nil,\n                accountOrganization: nil,\n                loginMethod: nil))\n\n        let output = CLIRenderer.renderText(\n            provider: .warp,\n            snapshot: snap,\n            credits: nil,\n            context: RenderContext(\n                header: \"Warp 0.0.0 (warp)\",\n                status: nil,\n                useColor: false,\n                resetStyle: .absolute))\n\n        #expect(output.contains(\"\\(meta.sessionLabel): 90% left\"))\n        #expect(output.contains(\"Resets\"))\n        #expect(output.contains(\"10/100 credits\"))\n        #expect(!output.contains(\"Resets 10/100 credits\"))\n    }\n\n    @Test\n    func `renders kilo plan activity and fallback note`() {\n        let now = Date(timeIntervalSince1970: 0)\n        let identity = ProviderIdentitySnapshot(\n            providerID: .kilo,\n            accountEmail: nil,\n            accountOrganization: nil,\n            loginMethod: \"Kilo Pass Pro · Auto top-up: visa\")\n        let snap = UsageSnapshot(\n            primary: .init(usedPercent: 40, windowMinutes: nil, resetsAt: nil, resetDescription: \"40/100 credits\"),\n            secondary: nil,\n            tertiary: nil,\n            updatedAt: now,\n            identity: identity)\n\n        let output = CLIRenderer.renderText(\n            provider: .kilo,\n            snapshot: snap,\n            credits: nil,\n            context: RenderContext(\n                header: \"Kilo (cli)\",\n                status: nil,\n                useColor: false,\n                resetStyle: .absolute,\n                notes: [\"Using CLI fallback\"]))\n\n        #expect(output.contains(\"Credits: 60% left\"))\n        #expect(output.contains(\"40/100 credits\"))\n        #expect(!output.contains(\"Resets 40/100 credits\"))\n        #expect(output.contains(\"Plan: Kilo Pass Pro\"))\n        #expect(output.contains(\"Activity: Auto top-up: visa\"))\n        #expect(output.contains(\"Note: Using CLI fallback\"))\n    }\n\n    @Test\n    func `renders kilo zero total edge state as detail`() {\n        let now = Date(timeIntervalSince1970: 0)\n        let snap = KiloUsageSnapshot(\n            creditsUsed: 0,\n            creditsTotal: 0,\n            creditsRemaining: 0,\n            planName: \"Kilo Pass Pro\",\n            autoTopUpEnabled: true,\n            autoTopUpMethod: \"visa\",\n            updatedAt: now).toUsageSnapshot()\n\n        let output = CLIRenderer.renderText(\n            provider: .kilo,\n            snapshot: snap,\n            credits: nil,\n            context: RenderContext(\n                header: \"Kilo (api)\",\n                status: nil,\n                useColor: false,\n                resetStyle: .absolute))\n\n        #expect(output.contains(\"Credits: 0% left\"))\n        #expect(output.contains(\"0/0 credits\"))\n        #expect(!output.contains(\"Resets 0/0 credits\"))\n    }\n\n    @Test\n    func `renders kilo auto top up only as activity without plan`() {\n        let now = Date(timeIntervalSince1970: 0)\n        let identity = ProviderIdentitySnapshot(\n            providerID: .kilo,\n            accountEmail: nil,\n            accountOrganization: nil,\n            loginMethod: \"Auto top-up: off\")\n        let snap = UsageSnapshot(\n            primary: nil,\n            secondary: nil,\n            tertiary: nil,\n            updatedAt: now,\n            identity: identity)\n\n        let output = CLIRenderer.renderText(\n            provider: .kilo,\n            snapshot: snap,\n            credits: nil,\n            context: RenderContext(\n                header: \"Kilo (cli)\",\n                status: nil,\n                useColor: false,\n                resetStyle: .absolute))\n\n        #expect(output.contains(\"Activity: Auto top-up: off\"))\n        #expect(!output.contains(\"Plan: Auto top-up: off\"))\n    }\n\n    @Test\n    func `renders pace line when weekly has reset`() {\n        let now = Date()\n        let snap = UsageSnapshot(\n            primary: nil,\n            secondary: .init(\n                usedPercent: 50,\n                windowMinutes: 10080,\n                resetsAt: now.addingTimeInterval(3 * 24 * 60 * 60),\n                resetDescription: nil),\n            tertiary: nil,\n            updatedAt: now)\n\n        let output = CLIRenderer.renderText(\n            provider: .codex,\n            snapshot: snap,\n            credits: nil,\n            context: RenderContext(\n                header: \"Codex 0.0.0 (codex-cli)\",\n                status: nil,\n                useColor: false,\n                resetStyle: .countdown))\n\n        #expect(output.contains(\"Pace:\"))\n    }\n\n    @Test\n    func `renders JSON payload`() throws {\n        let snap = UsageSnapshot(\n            primary: .init(usedPercent: 50, windowMinutes: 300, resetsAt: nil, resetDescription: nil),\n            secondary: .init(usedPercent: 10, windowMinutes: 10080, resetsAt: nil, resetDescription: nil),\n            tertiary: nil,\n            updatedAt: Date(timeIntervalSince1970: 1_700_000_000))\n\n        let payload = ProviderPayload(\n            provider: .codex,\n            account: nil,\n            version: \"1.2.3\",\n            source: \"codex-cli\",\n            status: ProviderStatusPayload(\n                indicator: .none,\n                description: nil,\n                updatedAt: Date(timeIntervalSince1970: 1_700_000_010),\n                url: \"https://status.example.com\"),\n            usage: snap,\n            credits: nil,\n            antigravityPlanInfo: nil,\n            openaiDashboard: nil,\n            error: nil)\n        let encoder = JSONEncoder()\n        encoder.dateEncodingStrategy = .secondsSince1970\n        let data = try encoder.encode(payload)\n        guard let json = String(data: data, encoding: .utf8) else {\n            Issue.record(\"Failed to decode JSON payload\")\n            return\n        }\n\n        #expect(json.contains(\"\\\"provider\\\":\\\"codex\\\"\"))\n        #expect(json.contains(\"\\\"version\\\":\\\"1.2.3\\\"\"))\n        #expect(json.contains(\"\\\"status\\\"\"))\n        #expect(json.contains(\"status.example.com\"))\n        #expect(json.contains(\"\\\"primary\\\"\"))\n        #expect(json.contains(\"\\\"windowMinutes\\\":300\"))\n        #expect(json.contains(\"1700000000\"))\n    }\n\n    @Test\n    func `encodes JSON with secondary null when missing`() throws {\n        let snap = UsageSnapshot(\n            primary: .init(usedPercent: 0, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            secondary: nil,\n            tertiary: nil,\n            updatedAt: Date(timeIntervalSince1970: 1_700_000_000))\n\n        let encoder = JSONEncoder()\n        encoder.dateEncodingStrategy = .secondsSince1970\n        let data = try encoder.encode(snap)\n        guard let json = String(data: data, encoding: .utf8) else {\n            Issue.record(\"Failed to decode JSON payload\")\n            return\n        }\n\n        #expect(json.contains(\"\\\"secondary\\\":null\"))\n    }\n\n    @Test\n    func `parses output format`() {\n        #expect(OutputFormat(argument: \"json\") == .json)\n        #expect(OutputFormat(argument: \"TEXT\") == .text)\n        #expect(OutputFormat(argument: \"invalid\") == nil)\n    }\n\n    @Test\n    func `defaults to usage when no command provided`() {\n        #expect(CodexBarCLI.effectiveArgv([]) == [\"usage\"])\n        #expect(CodexBarCLI.effectiveArgv([\"--format\", \"json\"]).first == \"usage\")\n        #expect(CodexBarCLI.effectiveArgv([\"usage\", \"--format\", \"json\"]).first == \"usage\")\n    }\n\n    @Test\n    func `status line is last and colored when TTY`() {\n        let identity = ProviderIdentitySnapshot(\n            providerID: .claude,\n            accountEmail: nil,\n            accountOrganization: nil,\n            loginMethod: \"pro\")\n        let snap = UsageSnapshot(\n            primary: .init(usedPercent: 0, windowMinutes: 300, resetsAt: nil, resetDescription: nil),\n            secondary: .init(usedPercent: 0, windowMinutes: 10080, resetsAt: nil, resetDescription: nil),\n            tertiary: nil,\n            updatedAt: Date(),\n            identity: identity)\n\n        let output = CLIRenderer.renderText(\n            provider: .claude,\n            snapshot: snap,\n            credits: nil,\n            context: RenderContext(\n                header: \"Claude Code 2.0.58 (claude)\",\n                status: ProviderStatusPayload(\n                    indicator: .critical,\n                    description: \"Major outage\",\n                    updatedAt: nil,\n                    url: \"https://status.claude.com\"),\n                useColor: true,\n                resetStyle: .absolute))\n\n        let lines = output.split(separator: \"\\n\")\n        #expect(lines.last?.contains(\"Status: Critical issue – Major outage\") == true)\n        #expect(output.contains(\"\\u{001B}[31mStatus\")) // red for critical\n    }\n\n    @Test\n    func `output has ansi when TTY even without status`() {\n        let snap = UsageSnapshot(\n            primary: .init(usedPercent: 1, windowMinutes: 300, resetsAt: nil, resetDescription: nil),\n            secondary: nil,\n            tertiary: nil,\n            updatedAt: Date(timeIntervalSince1970: 0))\n\n        let output = CLIRenderer.renderText(\n            provider: .codex,\n            snapshot: snap,\n            credits: nil,\n            context: RenderContext(\n                header: \"Codex 0.0.0 (codex-cli)\",\n                status: nil,\n                useColor: true,\n                resetStyle: .absolute))\n\n        #expect(output.contains(\"\\u{001B}[\"))\n    }\n\n    @Test\n    func `tty output colors header and usage`() {\n        let snap = UsageSnapshot(\n            primary: .init(usedPercent: 95, windowMinutes: 300, resetsAt: nil, resetDescription: nil),\n            secondary: .init(usedPercent: 80, windowMinutes: 10080, resetsAt: nil, resetDescription: nil),\n            tertiary: nil,\n            updatedAt: Date(timeIntervalSince1970: 0))\n\n        let output = CLIRenderer.renderText(\n            provider: .codex,\n            snapshot: snap,\n            credits: nil,\n            context: RenderContext(\n                header: \"Codex 0.0.0 (codex-cli)\",\n                status: nil,\n                useColor: true,\n                resetStyle: .absolute))\n\n        #expect(output.contains(\"\\u{001B}[1;95m== Codex 0.0.0 (codex-cli) ==\\u{001B}[0m\"))\n        #expect(output.contains(\"Session: \\u{001B}[31m5% left\\u{001B}[0m\")) // red <10% left\n        #expect(output.contains(\"Weekly: \\u{001B}[33m20% left\\u{001B}[0m\")) // yellow <25% left\n    }\n\n    @Test\n    func `status line is plain when no TTY`() {\n        let identity = ProviderIdentitySnapshot(\n            providerID: .codex,\n            accountEmail: nil,\n            accountOrganization: nil,\n            loginMethod: \"pro\")\n        let snap = UsageSnapshot(\n            primary: .init(usedPercent: 0, windowMinutes: 300, resetsAt: nil, resetDescription: nil),\n            secondary: .init(usedPercent: 0, windowMinutes: 10080, resetsAt: nil, resetDescription: nil),\n            tertiary: nil,\n            updatedAt: Date(),\n            identity: identity)\n\n        let output = CLIRenderer.renderText(\n            provider: .codex,\n            snapshot: snap,\n            credits: nil,\n            context: RenderContext(\n                header: \"Codex 0.6.0 (codex-cli)\",\n                status: ProviderStatusPayload(\n                    indicator: .none,\n                    description: \"Operational\",\n                    updatedAt: nil,\n                    url: \"https://status.openai.com/\"),\n                useColor: false,\n                resetStyle: .absolute))\n\n        #expect(!output.contains(\"\\u{001B}[\"))\n        #expect(output.contains(\"Status: Operational – Operational\"))\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/CLIWebFallbackTests.swift",
    "content": "import Testing\n@testable import CodexBarCLI\n@testable import CodexBarCore\n\nstruct CLIWebFallbackTests {\n    private func makeContext(\n        runtime: ProviderRuntime = .cli,\n        sourceMode: ProviderSourceMode = .auto,\n        settings: ProviderSettingsSnapshot? = nil) -> ProviderFetchContext\n    {\n        let browserDetection = BrowserDetection(cacheTTL: 0)\n        return ProviderFetchContext(\n            runtime: runtime,\n            sourceMode: sourceMode,\n            includeCredits: true,\n            webTimeout: 60,\n            webDebugDumpHTML: false,\n            verbose: false,\n            env: [:],\n            settings: settings,\n            fetcher: UsageFetcher(),\n            claudeFetcher: ClaudeUsageFetcher(browserDetection: browserDetection),\n            browserDetection: browserDetection)\n    }\n\n    private func makeClaudeSettingsSnapshot(cookieHeader: String?) -> ProviderSettingsSnapshot {\n        ProviderSettingsSnapshot.make(\n            claude: .init(\n                usageDataSource: .auto,\n                webExtrasEnabled: false,\n                cookieSource: .manual,\n                manualCookieHeader: cookieHeader))\n    }\n\n    @Test\n    func `codex falls back when cookies missing`() {\n        let context = self.makeContext()\n        let strategy = CodexWebDashboardStrategy()\n        #expect(strategy.shouldFallback(\n            on: OpenAIDashboardBrowserCookieImporter.ImportError.noCookiesFound,\n            context: context))\n        #expect(strategy.shouldFallback(\n            on: OpenAIDashboardBrowserCookieImporter.ImportError.noMatchingAccount(found: []),\n            context: context))\n        #expect(strategy.shouldFallback(\n            on: OpenAIDashboardBrowserCookieImporter.ImportError.browserAccessDenied(details: \"no access\"),\n            context: context))\n        #expect(strategy.shouldFallback(\n            on: OpenAIDashboardBrowserCookieImporter.ImportError.dashboardStillRequiresLogin,\n            context: context))\n        #expect(strategy.shouldFallback(\n            on: OpenAIDashboardFetcher.FetchError.loginRequired,\n            context: context))\n    }\n\n    @Test\n    func `codex falls back for dashboard data errors in auto`() {\n        let context = self.makeContext()\n        let strategy = CodexWebDashboardStrategy()\n        #expect(strategy.shouldFallback(\n            on: OpenAIDashboardFetcher.FetchError.noDashboardData(body: \"missing\"),\n            context: context))\n    }\n\n    @Test\n    func `claude falls back when no session key`() {\n        let context = self.makeContext()\n        let strategy = ClaudeWebFetchStrategy(browserDetection: BrowserDetection(cacheTTL: 0))\n        #expect(strategy.shouldFallback(on: ClaudeWebAPIFetcher.FetchError.noSessionKeyFound, context: context))\n        #expect(strategy.shouldFallback(on: ClaudeWebAPIFetcher.FetchError.unauthorized, context: context))\n    }\n\n    @Test\n    func `claude CLI fallback is enabled only for app auto`() {\n        let strategy = ClaudeCLIFetchStrategy(\n            useWebExtras: false,\n            manualCookieHeader: nil,\n            browserDetection: BrowserDetection(cacheTTL: 0))\n        let error = ClaudeUsageError.parseFailed(\"cli failed\")\n        let webAvailableSettings = self.makeClaudeSettingsSnapshot(cookieHeader: \"sessionKey=sk-ant-test\")\n        let webUnavailableSettings = self.makeClaudeSettingsSnapshot(cookieHeader: \"foo=bar\")\n\n        #expect(strategy.shouldFallback(\n            on: error,\n            context: self.makeContext(runtime: .app, sourceMode: .auto, settings: webAvailableSettings)))\n        #expect(!strategy.shouldFallback(\n            on: error,\n            context: self.makeContext(runtime: .app, sourceMode: .auto, settings: webUnavailableSettings)))\n        #expect(!strategy.shouldFallback(on: error, context: self.makeContext(runtime: .app, sourceMode: .cli)))\n        #expect(!strategy.shouldFallback(on: error, context: self.makeContext(runtime: .app, sourceMode: .web)))\n        #expect(!strategy.shouldFallback(on: error, context: self.makeContext(runtime: .app, sourceMode: .oauth)))\n        #expect(!strategy.shouldFallback(on: error, context: self.makeContext(runtime: .cli, sourceMode: .auto)))\n    }\n\n    @Test\n    func `claude web fallback is disabled for app auto`() {\n        let strategy = ClaudeWebFetchStrategy(browserDetection: BrowserDetection(cacheTTL: 0))\n        let error = ClaudeWebAPIFetcher.FetchError.unauthorized\n        #expect(strategy.shouldFallback(on: error, context: self.makeContext(runtime: .cli, sourceMode: .auto)))\n        #expect(!strategy.shouldFallback(on: error, context: self.makeContext(runtime: .app, sourceMode: .auto)))\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/ClaudeBaselineCharacterizationTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\n@Suite(.serialized)\nstruct ClaudeBaselineCharacterizationTests {\n    private func makeStubClaudeCLI() throws -> String {\n        let sample = \"\"\"\n        Current session\n        12% used  (Resets 11am)\n        Current week (all models)\n        40% used  (Resets Nov 21)\n        Current week (Sonnet only)\n        5% used (Resets Nov 21)\n        Account: user@example.com\n        Org: Example Org\n        \"\"\"\n        let script = \"\"\"\n        #!/bin/sh\n        cat <<'EOF'\n        \\(sample)\n        EOF\n        \"\"\"\n        let url = FileManager.default.temporaryDirectory\n            .appendingPathComponent(\"claude-stub-\\(UUID().uuidString)\")\n        try Data(script.utf8).write(to: url)\n        try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: url.path)\n        return url.path\n    }\n\n    private func makeContext(\n        runtime: ProviderRuntime,\n        sourceMode: ProviderSourceMode,\n        env: [String: String] = [:],\n        settings: ProviderSettingsSnapshot? = nil) -> ProviderFetchContext\n    {\n        let browserDetection = BrowserDetection(cacheTTL: 0)\n        return ProviderFetchContext(\n            runtime: runtime,\n            sourceMode: sourceMode,\n            includeCredits: false,\n            webTimeout: 1,\n            webDebugDumpHTML: false,\n            verbose: false,\n            env: env,\n            settings: settings,\n            fetcher: UsageFetcher(environment: env),\n            claudeFetcher: ClaudeUsageFetcher(browserDetection: browserDetection),\n            browserDetection: browserDetection)\n    }\n\n    private func strategyIDs(\n        runtime: ProviderRuntime,\n        sourceMode: ProviderSourceMode,\n        env: [String: String] = [:],\n        settings: ProviderSettingsSnapshot? = nil) async -> [String]\n    {\n        let descriptor = ProviderDescriptorRegistry.descriptor(for: .claude)\n        let context = self.makeContext(runtime: runtime, sourceMode: sourceMode, env: env, settings: settings)\n        let strategies = await descriptor.fetchPlan.pipeline.resolveStrategies(context)\n        return strategies.map(\\.id)\n    }\n\n    private func fetchOutcome(\n        runtime: ProviderRuntime,\n        sourceMode: ProviderSourceMode,\n        env: [String: String] = [:],\n        settings: ProviderSettingsSnapshot? = nil) async -> ProviderFetchOutcome\n    {\n        let descriptor = ProviderDescriptorRegistry.descriptor(for: .claude)\n        let context = self.makeContext(runtime: runtime, sourceMode: sourceMode, env: env, settings: settings)\n        return await descriptor.fetchPlan.fetchOutcome(context: context, provider: .claude)\n    }\n\n    private func withNoOAuthCredentials<T>(operation: () async throws -> T) async rethrows -> T {\n        let missingCredentialsURL = FileManager.default.temporaryDirectory\n            .appendingPathComponent(\"missing-claude-creds-\\(UUID().uuidString).json\")\n        return try await KeychainCacheStore.withServiceOverrideForTesting(\"rat-110-\\(UUID().uuidString)\") {\n            KeychainCacheStore.setTestStoreForTesting(true)\n            defer { KeychainCacheStore.setTestStoreForTesting(false) }\n            return try await ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting {\n                try await ClaudeOAuthCredentialsStore.withIsolatedCredentialsFileTrackingForTesting {\n                    try await ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(missingCredentialsURL) {\n                        try await ClaudeOAuthCredentialsStore.withKeychainAccessOverrideForTesting(false) {\n                            try await ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(\n                                data: nil,\n                                fingerprint: nil)\n                            {\n                                try await operation()\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    @Test\n    func `app auto pipeline order is OAuth then CLI then web`() async {\n        let settings = ProviderSettingsSnapshot.make(claude: .init(\n            usageDataSource: .auto,\n            webExtrasEnabled: true,\n            cookieSource: .manual,\n            manualCookieHeader: \"sessionKey=sk-ant-session-token\"))\n        let env = [\n            ClaudeOAuthCredentialsStore.environmentTokenKey: \"oauth-token\",\n            ClaudeOAuthCredentialsStore.environmentScopesKey: \"user:profile\",\n            \"CLAUDE_CLI_PATH\": \"/usr/bin/true\",\n        ]\n        let strategyIDs = await self.strategyIDs(runtime: .app, sourceMode: .auto, env: env, settings: settings)\n        #expect(strategyIDs == [\"claude.oauth\", \"claude.cli\", \"claude.web\"])\n    }\n\n    @Test\n    func `CLI auto pipeline order is web then CLI`() async {\n        let settings = ProviderSettingsSnapshot.make(claude: .init(\n            usageDataSource: .auto,\n            webExtrasEnabled: false,\n            cookieSource: .manual,\n            manualCookieHeader: \"sessionKey=sk-ant-session-token\"))\n        let env = [\n            \"CLAUDE_CLI_PATH\": \"/usr/bin/true\",\n        ]\n        let strategyIDs = await self.strategyIDs(runtime: .cli, sourceMode: .auto, env: env, settings: settings)\n        #expect(strategyIDs == [\"claude.web\", \"claude.cli\"])\n    }\n\n    @Test\n    func `explicit CLI pipeline attempts strategy even when planner marks CLI unavailable`() async {\n        let settings = ProviderSettingsSnapshot.make(claude: .init(\n            usageDataSource: .cli,\n            webExtrasEnabled: false,\n            cookieSource: .off,\n            manualCookieHeader: nil))\n        let env = [\n            \"CLAUDE_CLI_PATH\": \"/definitely/missing/claude\",\n        ]\n        let descriptor = ProviderDescriptorRegistry.descriptor(for: .claude)\n        let context = self.makeContext(runtime: .app, sourceMode: .cli, env: env, settings: settings)\n        let strategies = await descriptor.fetchPlan.pipeline.resolveStrategies(context)\n\n        #expect(strategies.map(\\.id) == [\"claude.cli\"])\n        #expect(await strategies[0].isAvailable(context))\n    }\n\n    @Test\n    func `auto pipeline records unavailable planned steps when planner has no executable source`() async {\n        let settings = ProviderSettingsSnapshot.make(claude: .init(\n            usageDataSource: .auto,\n            webExtrasEnabled: true,\n            cookieSource: .off,\n            manualCookieHeader: nil))\n        let env = [\"CLAUDE_CLI_PATH\": \"/definitely/missing/claude\"]\n\n        await self.withNoOAuthCredentials {\n            await ClaudeCLIResolver.withResolvedBinaryPathOverrideForTesting(\"/definitely/missing/claude\") {\n                let strategyIDs = await self.strategyIDs(runtime: .app, sourceMode: .auto, env: env, settings: settings)\n                #expect(strategyIDs == [\"claude.oauth\", \"claude.cli\", \"claude.web\"])\n\n                let outcome = await self.fetchOutcome(runtime: .app, sourceMode: .auto, env: env, settings: settings)\n                #expect(outcome.attempts.map(\\.strategyID) == [\"claude.oauth\", \"claude.cli\", \"claude.web\"])\n                #expect(outcome.attempts.map(\\.wasAvailable) == [false, false, false])\n\n                switch outcome.result {\n                case let .failure(error as ProviderFetchError):\n                    switch error {\n                    case let .noAvailableStrategy(provider):\n                        #expect(provider == .claude)\n                    }\n                case let .failure(error):\n                    Issue.record(\"Unexpected failure: \\(error)\")\n                case let .success(result):\n                    Issue.record(\"Unexpected success: \\(result.sourceLabel)\")\n                }\n            }\n        }\n    }\n\n    @Test\n    func `app auto pipeline retains OAuth bootstrap strategy at startup`() async {\n        let settings = ProviderSettingsSnapshot.make(claude: .init(\n            usageDataSource: .auto,\n            webExtrasEnabled: false,\n            cookieSource: .off,\n            manualCookieHeader: nil))\n\n        await ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting {\n            ClaudeOAuthCredentialsStore.invalidateCache()\n            ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n            ClaudeOAuthKeychainAccessGate.resetForTesting()\n            defer {\n                ClaudeOAuthCredentialsStore.invalidateCache()\n                ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                ClaudeOAuthKeychainAccessGate.resetForTesting()\n            }\n\n            await self.withNoOAuthCredentials {\n                let strategyIDs = await ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(\n                    .onlyOnUserAction)\n                {\n                    await ProviderRefreshContext.$current.withValue(.startup) {\n                        await ProviderInteractionContext.$current.withValue(.background) {\n                            await self.strategyIDs(runtime: .app, sourceMode: .auto, settings: settings)\n                        }\n                    }\n                }\n                #expect(strategyIDs.first == \"claude.oauth\")\n                #expect(strategyIDs.contains(\"claude.oauth\"))\n            }\n        }\n    }\n\n    @Test\n    func `auto pipeline CLI uses planned environment for execution`() async throws {\n        let settings = ProviderSettingsSnapshot.make(claude: .init(\n            usageDataSource: .auto,\n            webExtrasEnabled: false,\n            cookieSource: .off,\n            manualCookieHeader: nil))\n        let stubCLIPath = try self.makeStubClaudeCLI()\n        let env = [\"CLAUDE_CLI_PATH\": stubCLIPath]\n\n        await self.withNoOAuthCredentials {\n            let fetchOverride: @Sendable (String, TimeInterval, Bool) async throws\n                -> ClaudeStatusSnapshot = { binary, _, _ in\n                    #expect(binary == stubCLIPath)\n                    return ClaudeStatusSnapshot(\n                        sessionPercentLeft: 88,\n                        weeklyPercentLeft: 60,\n                        opusPercentLeft: 95,\n                        accountEmail: \"user@example.com\",\n                        accountOrganization: \"Example Org\",\n                        loginMethod: nil,\n                        primaryResetDescription: \"Resets 11am\",\n                        secondaryResetDescription: \"Resets Nov 21\",\n                        opusResetDescription: \"Resets Nov 21\",\n                        rawText: \"stub\")\n                }\n            let outcome = await ClaudeStatusProbe.$fetchOverride.withValue(fetchOverride) {\n                await self.fetchOutcome(runtime: .app, sourceMode: .auto, env: env, settings: settings)\n            }\n\n            #expect(outcome.attempts.map(\\.strategyID) == [\"claude.oauth\", \"claude.cli\"])\n            #expect(outcome.attempts.map(\\.wasAvailable) == [false, true])\n\n            switch outcome.result {\n            case let .success(result):\n                #expect(result.strategyID == \"claude.cli\")\n                #expect(result.sourceLabel == \"claude\")\n                #expect(result.usage.primary?.usedPercent == 12)\n                #expect(result.usage.secondary?.usedPercent == 40)\n                #expect(result.usage.tertiary?.usedPercent == 5)\n                #expect(result.usage.identity?.accountEmail == \"user@example.com\")\n            case let .failure(error):\n                Issue.record(\"Unexpected failure: \\(error)\")\n            }\n        }\n    }\n\n    @Test(arguments: [\n        (ProviderSourceMode.oauth, \"claude.oauth\"),\n        (ProviderSourceMode.cli, \"claude.cli\"),\n        (ProviderSourceMode.web, \"claude.web\"),\n    ])\n    func `explicit modes resolve single Claude strategy`(\n        sourceMode: ProviderSourceMode,\n        expectedStrategyID: String) async\n    {\n        let strategyIDs = await self.strategyIDs(runtime: .app, sourceMode: sourceMode)\n        #expect(strategyIDs == [expectedStrategyID])\n    }\n\n    @Test(arguments: [\n        (ProviderSourceMode.oauth, \"claude.oauth\"),\n        (ProviderSourceMode.cli, \"claude.cli\"),\n        (ProviderSourceMode.web, \"claude.web\"),\n    ])\n    func `CLI explicit modes resolve single Claude strategy`(\n        sourceMode: ProviderSourceMode,\n        expectedStrategyID: String) async\n    {\n        let strategyIDs = await self.strategyIDs(runtime: .cli, sourceMode: sourceMode)\n        #expect(strategyIDs == [expectedStrategyID])\n    }\n\n    @Test\n    func `Claude OAuth token heuristics accept raw and bearer inputs`() {\n        #expect(TokenAccountSupportCatalog.isClaudeOAuthToken(\"sk-ant-oat-test-token\"))\n        #expect(TokenAccountSupportCatalog.isClaudeOAuthToken(\"Bearer sk-ant-oat-test-token\"))\n    }\n\n    @Test\n    func `Claude OAuth token heuristics reject cookie shaped inputs`() {\n        #expect(!TokenAccountSupportCatalog.isClaudeOAuthToken(\"sessionKey=sk-ant-session\"))\n        #expect(!TokenAccountSupportCatalog.isClaudeOAuthToken(\"Cookie: sessionKey=sk-ant-session; foo=bar\"))\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/ClaudeCredentialRoutingTests.swift",
    "content": "import CodexBarCore\nimport Testing\n\nstruct ClaudeCredentialRoutingTests {\n    @Test\n    func `resolves raw OAuth token`() {\n        let routing = ClaudeCredentialRouting.resolve(\n            tokenAccountToken: \"sk-ant-oat-test-token\",\n            manualCookieHeader: nil)\n\n        #expect(routing == .oauth(accessToken: \"sk-ant-oat-test-token\"))\n    }\n\n    @Test\n    func `resolves bearer OAuth token`() {\n        let routing = ClaudeCredentialRouting.resolve(\n            tokenAccountToken: \"Bearer sk-ant-oat-test-token\",\n            manualCookieHeader: nil)\n\n        #expect(routing == .oauth(accessToken: \"sk-ant-oat-test-token\"))\n    }\n\n    @Test\n    func `resolves session token to cookie header`() {\n        let routing = ClaudeCredentialRouting.resolve(\n            tokenAccountToken: \"sk-ant-session-token\",\n            manualCookieHeader: nil)\n\n        #expect(routing == .webCookie(header: \"sessionKey=sk-ant-session-token\"))\n    }\n\n    @Test\n    func `resolves config cookie header through shared normalizer`() {\n        let routing = ClaudeCredentialRouting.resolve(\n            tokenAccountToken: nil,\n            manualCookieHeader: \"Cookie: sessionKey=sk-ant-session-token; foo=bar\")\n\n        #expect(routing == .webCookie(header: \"sessionKey=sk-ant-session-token; foo=bar\"))\n    }\n\n    @Test\n    func `token account input wins over config cookie fallback`() {\n        let routing = ClaudeCredentialRouting.resolve(\n            tokenAccountToken: \"Bearer sk-ant-oat-test-token\",\n            manualCookieHeader: \"Cookie: sessionKey=sk-ant-session-token\")\n\n        #expect(routing == .oauth(accessToken: \"sk-ant-oat-test-token\"))\n    }\n\n    @Test\n    func `empty inputs resolve to none`() {\n        let routing = ClaudeCredentialRouting.resolve(\n            tokenAccountToken: \"   \",\n            manualCookieHeader: \"\\n\")\n\n        #expect(routing == .none)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/ClaudeDebugDiagnosticsTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBar\n@testable import CodexBarCore\n\n@Suite(.serialized)\nstruct ClaudeDebugDiagnosticsTests {\n    private func makeCredentialsData(\n        accessToken: String,\n        expiresAt: Date,\n        refreshToken: String? = nil) -> Data\n    {\n        let refreshTokenLine = if let refreshToken {\n            \"\"\"\n                \"refreshToken\": \"\\(refreshToken)\",\n            \"\"\"\n        } else {\n            \"\"\n        }\n        let json = \"\"\"\n        {\n          \"claudeAiOauth\": {\n            \"accessToken\": \"\\(accessToken)\",\n        \\(refreshTokenLine)\n            \"expiresAt\": \\(Int(expiresAt.timeIntervalSince1970 * 1000)),\n            \"scopes\": [\"user:profile\"]\n          }\n        }\n        \"\"\"\n        return Data(json.utf8)\n    }\n\n    @Test\n    func `debug log uses planner derived order and reasons`() async throws {\n        let suite = \"ClaudeDebugDiagnosticsTests-\\(UUID().uuidString)\"\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n        let tempDir = FileManager.default.temporaryDirectory\n            .appendingPathComponent(UUID().uuidString, isDirectory: true)\n        try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n        let credentialsURL = tempDir.appendingPathComponent(\"credentials.json\")\n        let credsJSON = \"\"\"\n        {\n          \"claudeAiOauth\": {\n            \"accessToken\": \"oauth-token\",\n            \"expiresAt\": \\(Int(Date(timeIntervalSinceNow: 3600).timeIntervalSince1970 * 1000)),\n            \"scopes\": [\"user:profile\"]\n          }\n        }\n        \"\"\"\n        try Data(credsJSON.utf8).write(to: credentialsURL)\n\n        let store = try await MainActor.run { () -> UsageStore in\n            let defaults = try #require(UserDefaults(suiteName: suite))\n            defaults.removePersistentDomain(forName: suite)\n            let configStore = testConfigStore(suiteName: suite)\n            let settings = SettingsStore(\n                userDefaults: defaults,\n                configStore: configStore,\n                zaiTokenStore: NoopZaiTokenStore())\n            settings.claudeUsageDataSource = .auto\n            settings.claudeCookieSource = .manual\n            settings.claudeCookieHeader = \"sessionKey=sk-ant-session-token\"\n\n            return UsageStore(\n                fetcher: UsageFetcher(),\n                browserDetection: BrowserDetection(cacheTTL: 0),\n                settings: settings)\n        }\n\n        let text = await KeychainCacheStore.withServiceOverrideForTesting(service) {\n            KeychainCacheStore.setTestStoreForTesting(true)\n            defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n            return await ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting {\n                await ClaudeOAuthCredentialsStore.withIsolatedCredentialsFileTrackingForTesting {\n                    await ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(credentialsURL) {\n                        await store.debugLog(for: .claude)\n                    }\n                }\n            }\n        }\n\n        #expect(text.contains(\"planner_order=oauth→cli→web\"))\n        #expect(text.contains(\"planner_selected=oauth\"))\n        #expect(text.contains(\"planner_no_source=false\"))\n        #expect(text.contains(\"planner_step.oauth=available reason=app-auto-preferred-oauth\"))\n        #expect(text.contains(\"planner_step.cli=\"))\n        #expect(text.contains(\"reason=app-auto-fallback-cli\"))\n        #expect(text.contains(\"planner_step.web=available reason=app-auto-fallback-web\"))\n        #expect(!text.contains(\"auto_heuristic=\"))\n    }\n\n    @Test\n    func `debug log reports no planner selected source when auto has no available sources`() async throws {\n        let suite = \"ClaudeDebugDiagnosticsTests-\\(UUID().uuidString)\"\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n        let tempDir = FileManager.default.temporaryDirectory\n            .appendingPathComponent(UUID().uuidString, isDirectory: true)\n        try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n        let missingCredentialsURL = tempDir.appendingPathComponent(\"missing-credentials.json\")\n\n        let store = try await MainActor.run { () -> UsageStore in\n            let defaults = try #require(UserDefaults(suiteName: suite))\n            defaults.removePersistentDomain(forName: suite)\n            let configStore = testConfigStore(suiteName: suite)\n            let settings = SettingsStore(\n                userDefaults: defaults,\n                configStore: configStore,\n                zaiTokenStore: NoopZaiTokenStore())\n            settings.claudeUsageDataSource = .auto\n            settings.claudeCookieSource = .off\n            settings.claudeWebExtrasEnabled = true\n\n            return UsageStore(\n                fetcher: UsageFetcher(),\n                browserDetection: BrowserDetection(cacheTTL: 0),\n                settings: settings)\n        }\n\n        let text = await ClaudeCLIResolver.withResolvedBinaryPathOverrideForTesting(\"/definitely/missing/claude\") {\n            await KeychainCacheStore.withServiceOverrideForTesting(service) {\n                KeychainCacheStore.setTestStoreForTesting(true)\n                defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n                return await ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting {\n                    await ClaudeOAuthCredentialsStore.withIsolatedCredentialsFileTrackingForTesting {\n                        await ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(missingCredentialsURL) {\n                            await ClaudeOAuthCredentialsStore.withKeychainAccessOverrideForTesting(false) {\n                                await ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(\n                                    data: nil,\n                                    fingerprint: nil)\n                                {\n                                    await store.debugLog(for: .claude)\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        #expect(text.contains(\"planner_selected=none\"))\n        #expect(text.contains(\"planner_no_source=true\"))\n        #expect(text.contains(\"No planner-selected Claude source.\"))\n        #expect(!text.contains(\"web_extras=enabled\"))\n    }\n\n    @Test\n    func `debug Claude dump returns recorded parse dumps`() async {\n        await ClaudeStatusProbe._replaceDumpsForTesting([\n            \"dump one\",\n            \"dump two\",\n        ])\n\n        let store = await MainActor.run {\n            UsageStore(\n                fetcher: UsageFetcher(),\n                browserDetection: BrowserDetection(cacheTTL: 0),\n                settings: SettingsStore(\n                    userDefaults: UserDefaults(),\n                    configStore: testConfigStore(suiteName: \"ClaudeDebugDiagnosticsTests-\\(UUID().uuidString)\"),\n                    zaiTokenStore: NoopZaiTokenStore()))\n        }\n        let text = await store.debugClaudeDump()\n\n        #expect(text.contains(\"dump one\"))\n        #expect(text.contains(\"dump two\"))\n        #expect(!text.contains(\"planner_order=\"))\n        await ClaudeStatusProbe._replaceDumpsForTesting([])\n    }\n\n    @Test\n    func `debug log uses runtime OAuth availability for token account routing`() async throws {\n        let suite = \"ClaudeDebugDiagnosticsTests-\\(UUID().uuidString)\"\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n        let tempDir = FileManager.default.temporaryDirectory\n            .appendingPathComponent(UUID().uuidString, isDirectory: true)\n        try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n        let missingCredentialsURL = tempDir.appendingPathComponent(\"missing-credentials.json\")\n\n        let store = try await MainActor.run { () -> UsageStore in\n            let defaults = try #require(UserDefaults(suiteName: suite))\n            defaults.removePersistentDomain(forName: suite)\n            let configStore = testConfigStore(suiteName: suite)\n            let settings = SettingsStore(\n                userDefaults: defaults,\n                configStore: configStore,\n                zaiTokenStore: NoopZaiTokenStore())\n            settings.claudeUsageDataSource = .auto\n            settings.claudeCookieSource = .off\n            settings.addTokenAccount(\n                provider: .claude,\n                label: \"OAuth Account\",\n                token: \"sk-ant-oat-test-token\")\n\n            return UsageStore(\n                fetcher: UsageFetcher(),\n                browserDetection: BrowserDetection(cacheTTL: 0),\n                settings: settings)\n        }\n\n        let text = await ClaudeCLIResolver.withResolvedBinaryPathOverrideForTesting(\"/definitely/missing/claude\") {\n            await KeychainCacheStore.withServiceOverrideForTesting(service) {\n                KeychainCacheStore.setTestStoreForTesting(true)\n                defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n                return await ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting {\n                    await ClaudeOAuthCredentialsStore.withIsolatedCredentialsFileTrackingForTesting {\n                        await ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(missingCredentialsURL) {\n                            await ClaudeOAuthCredentialsStore.withKeychainAccessOverrideForTesting(false) {\n                                await ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(\n                                    data: nil,\n                                    fingerprint: nil)\n                                {\n                                    await store.debugLog(for: .claude)\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        #expect(text.contains(\"planner_selected=oauth\"))\n        #expect(text.contains(\"hasOAuthCredentials=true\"))\n        #expect(text.contains(\"oauthCredentialOwner=environment\"))\n        #expect(text.contains(\"oauthCredentialSource=environment\"))\n        #expect(!text.contains(\"planner_selected=none\"))\n    }\n\n    @Test\n    func `debug log preserves CLI probe overrides across detached work`() async throws {\n        let suite = \"ClaudeDebugDiagnosticsTests-\\(UUID().uuidString)\"\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n        let tempDir = FileManager.default.temporaryDirectory\n            .appendingPathComponent(UUID().uuidString, isDirectory: true)\n        try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n        let missingCredentialsURL = tempDir.appendingPathComponent(\"missing-credentials.json\")\n        let store = try await MainActor.run { () -> UsageStore in\n            let defaults = try #require(UserDefaults(suiteName: suite))\n            defaults.removePersistentDomain(forName: suite)\n            let configStore = testConfigStore(suiteName: suite)\n            let settings = SettingsStore(\n                userDefaults: defaults,\n                configStore: configStore,\n                zaiTokenStore: NoopZaiTokenStore())\n            settings.claudeUsageDataSource = .cli\n            settings.claudeCookieSource = .off\n\n            return UsageStore(\n                fetcher: UsageFetcher(),\n                browserDetection: BrowserDetection(cacheTTL: 0),\n                settings: settings)\n        }\n\n        let fetchOverride: ClaudeStatusProbe.FetchOverride = { resolved, _, _ in\n            #expect(resolved == \"/usr/bin/true\")\n            return ClaudeStatusSnapshot(\n                sessionPercentLeft: 76,\n                weeklyPercentLeft: 55,\n                opusPercentLeft: nil,\n                accountEmail: \"cli@example.com\",\n                accountOrganization: \"CLI Org\",\n                loginMethod: \"cli\",\n                primaryResetDescription: \"Mar 7 at 1pm\",\n                secondaryResetDescription: nil,\n                opusResetDescription: nil,\n                rawText: \"probe raw\")\n        }\n\n        let text = await ClaudeCLIResolver.withResolvedBinaryPathOverrideForTesting(\"/usr/bin/true\") {\n            await KeychainCacheStore.withServiceOverrideForTesting(service) {\n                KeychainCacheStore.setTestStoreForTesting(true)\n                defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n                return await ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting {\n                    await ClaudeOAuthCredentialsStore.withIsolatedCredentialsFileTrackingForTesting {\n                        await ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(missingCredentialsURL) {\n                            await ClaudeOAuthCredentialsStore.withKeychainAccessOverrideForTesting(false) {\n                                await ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(\n                                    data: nil,\n                                    fingerprint: nil)\n                                {\n                                    await ClaudeStatusProbe.withFetchOverrideForTesting(fetchOverride) {\n                                        await store.debugLog(for: .claude)\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        #expect(text.contains(\"planner_selected=cli\"))\n        #expect(text.contains(\"session_left=76.0 weekly_left=55.0\"))\n        #expect(text.contains(\"email cli@example.com\"))\n    }\n\n    @Test\n    func `debug log uses user initiated interaction for OAuth prompt gate`() async throws {\n        let suite = \"ClaudeDebugDiagnosticsTests-\\(UUID().uuidString)\"\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n        let tempDir = FileManager.default.temporaryDirectory\n            .appendingPathComponent(UUID().uuidString, isDirectory: true)\n        try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n        let missingCredentialsURL = tempDir.appendingPathComponent(\"missing-credentials.json\")\n        let securityData = self.makeCredentialsData(\n            accessToken: \"user-initiated-oauth\",\n            expiresAt: Date(timeIntervalSinceNow: 3600))\n        let deniedStore = ClaudeOAuthKeychainAccessGate.DeniedUntilStore()\n        deniedStore.deniedUntil = Date(timeIntervalSinceNow: 300)\n\n        let store = try await MainActor.run { () -> UsageStore in\n            let defaults = try #require(UserDefaults(suiteName: suite))\n            defaults.removePersistentDomain(forName: suite)\n            let configStore = testConfigStore(suiteName: suite)\n            let settings = SettingsStore(\n                userDefaults: defaults,\n                configStore: configStore,\n                zaiTokenStore: NoopZaiTokenStore())\n            settings.claudeUsageDataSource = .auto\n            settings.claudeCookieSource = .off\n\n            return UsageStore(\n                fetcher: UsageFetcher(),\n                browserDetection: BrowserDetection(cacheTTL: 0),\n                settings: settings)\n        }\n\n        let text = await ClaudeCLIResolver.withResolvedBinaryPathOverrideForTesting(\"/definitely/missing/claude\") {\n            await KeychainCacheStore.withServiceOverrideForTesting(service) {\n                KeychainCacheStore.setTestStoreForTesting(true)\n                defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n                return await ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting {\n                    await ClaudeOAuthCredentialsStore.withIsolatedCredentialsFileTrackingForTesting {\n                        await ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(missingCredentialsURL) {\n                            await ClaudeOAuthKeychainAccessGate.withDeniedUntilStoreOverrideForTesting(deniedStore) {\n                                await ClaudeOAuthKeychainPromptPreference\n                                    .withTaskOverrideForTesting(.onlyOnUserAction) {\n                                        await ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(\n                                            .securityCLIExperimental)\n                                        {\n                                            await ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting(\n                                                .data(securityData))\n                                            {\n                                                await ProviderInteractionContext.$current.withValue(.userInitiated) {\n                                                    await store.debugLog(for: .claude)\n                                                }\n                                            }\n                                        }\n                                    }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        #expect(text.contains(\"planner_selected=oauth\"))\n        #expect(text.contains(\"hasOAuthCredentials=true\"))\n    }\n\n    @Test\n    func `debug log invalidates cached planner output when Claude settings change`() async throws {\n        let suite = \"ClaudeDebugDiagnosticsTests-\\(UUID().uuidString)\"\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n        let tempDir = FileManager.default.temporaryDirectory\n            .appendingPathComponent(UUID().uuidString, isDirectory: true)\n        try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n        let missingCredentialsURL = tempDir.appendingPathComponent(\"missing-credentials.json\")\n        let storeAndSettings = try await MainActor.run { () -> (UsageStore, SettingsStore) in\n            let defaults = try #require(UserDefaults(suiteName: suite))\n            defaults.removePersistentDomain(forName: suite)\n            let configStore = testConfigStore(suiteName: suite)\n            let settings = SettingsStore(\n                userDefaults: defaults,\n                configStore: configStore,\n                zaiTokenStore: NoopZaiTokenStore())\n            settings.claudeUsageDataSource = .cli\n            settings.claudeCookieSource = .off\n\n            let store = UsageStore(\n                fetcher: UsageFetcher(),\n                browserDetection: BrowserDetection(cacheTTL: 0),\n                settings: settings)\n            return (store, settings)\n        }\n        let store = storeAndSettings.0\n        let settings = storeAndSettings.1\n        let fetchOverride: ClaudeStatusProbe.FetchOverride = { _, _, _ in\n            ClaudeStatusSnapshot(\n                sessionPercentLeft: 80,\n                weeklyPercentLeft: 60,\n                opusPercentLeft: nil,\n                accountEmail: \"cache@example.com\",\n                accountOrganization: nil,\n                loginMethod: \"cli\",\n                primaryResetDescription: \"Mar 7 at 1pm\",\n                secondaryResetDescription: nil,\n                opusResetDescription: nil,\n                rawText: \"probe raw\")\n        }\n\n        let first = await ClaudeCLIResolver.withResolvedBinaryPathOverrideForTesting(\"/usr/bin/true\") {\n            await KeychainCacheStore.withServiceOverrideForTesting(service) {\n                KeychainCacheStore.setTestStoreForTesting(true)\n                defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n                return await ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting {\n                    await ClaudeOAuthCredentialsStore.withIsolatedCredentialsFileTrackingForTesting {\n                        await ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(missingCredentialsURL) {\n                            await ClaudeOAuthCredentialsStore.withKeychainAccessOverrideForTesting(false) {\n                                await ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(\n                                    data: nil,\n                                    fingerprint: nil)\n                                {\n                                    await ClaudeStatusProbe.withFetchOverrideForTesting(fetchOverride) {\n                                        await store.debugLog(for: .claude)\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        await MainActor.run {\n            settings.claudeUsageDataSource = .auto\n        }\n        await Task.yield()\n        await Task.yield()\n\n        let second = await ClaudeCLIResolver.withResolvedBinaryPathOverrideForTesting(\"/definitely/missing/claude\") {\n            await KeychainCacheStore.withServiceOverrideForTesting(service) {\n                KeychainCacheStore.setTestStoreForTesting(true)\n                defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n                return await ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting {\n                    await ClaudeOAuthCredentialsStore.withIsolatedCredentialsFileTrackingForTesting {\n                        await ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(missingCredentialsURL) {\n                            await ClaudeOAuthCredentialsStore.withKeychainAccessOverrideForTesting(false) {\n                                await ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(\n                                    data: nil,\n                                    fingerprint: nil)\n                                {\n                                    await store.debugLog(for: .claude)\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        #expect(first.contains(\"planner_selected=cli\"))\n        #expect(second.contains(\"planner_selected=none\"))\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/ClaudeOAuthCredentialsStorePromptPolicyTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\n@Suite(.serialized)\nstruct ClaudeOAuthCredentialsStorePromptPolicyTests {\n    private func makeCredentialsData(accessToken: String, expiresAt: Date, refreshToken: String? = nil) -> Data {\n        let millis = Int(expiresAt.timeIntervalSince1970 * 1000)\n        let refreshField: String = {\n            guard let refreshToken else { return \"\" }\n            return \",\\n            \\\"refreshToken\\\": \\\"\\(refreshToken)\\\"\"\n        }()\n        let json = \"\"\"\n        {\n          \"claudeAiOauth\": {\n            \"accessToken\": \"\\(accessToken)\",\n            \"expiresAt\": \\(millis),\n            \"scopes\": [\"user:profile\"]\\(refreshField)\n          }\n        }\n        \"\"\"\n        return Data(json.utf8)\n    }\n\n    @Test\n    func `does not read claude keychain in background when prompt mode only on user action`() throws {\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n        try KeychainCacheStore.withServiceOverrideForTesting(service) {\n            try KeychainAccessGate.withTaskOverrideForTesting(false) {\n                KeychainCacheStore.setTestStoreForTesting(true)\n                defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n                ClaudeOAuthCredentialsStore.invalidateCache()\n                ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                defer {\n                    ClaudeOAuthCredentialsStore.invalidateCache()\n                    ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                    ClaudeOAuthCredentialsStore._resetClaudeKeychainChangeTrackingForTesting()\n                }\n\n                let tempDir = FileManager.default.temporaryDirectory\n                    .appendingPathComponent(UUID().uuidString, isDirectory: true)\n                try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n                let fileURL = tempDir.appendingPathComponent(\"credentials.json\")\n\n                try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {\n                    ClaudeOAuthCredentialsStore._resetClaudeKeychainChangeTrackingForTesting()\n\n                    let fingerprint = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                        modifiedAt: 1,\n                        createdAt: 1,\n                        persistentRefHash: \"ref1\")\n                    let keychainData = self.makeCredentialsData(\n                        accessToken: \"keychain-token\",\n                        expiresAt: Date(timeIntervalSinceNow: 3600))\n\n                    do {\n                        _ = try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.onlyOnUserAction) {\n                            try ProviderInteractionContext.$current.withValue(.background) {\n                                try ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(\n                                    data: keychainData,\n                                    fingerprint: fingerprint)\n                                {\n                                    try ClaudeOAuthCredentialsStore.load(environment: [:], allowKeychainPrompt: false)\n                                }\n                            }\n                        }\n                        Issue.record(\"Expected ClaudeOAuthCredentialsError.notFound\")\n                    } catch let error as ClaudeOAuthCredentialsError {\n                        guard case .notFound = error else {\n                            Issue.record(\"Expected .notFound, got \\(error)\")\n                            return\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    @Test\n    func `can read claude keychain on user action when prompt mode only on user action`() throws {\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n        try KeychainCacheStore.withServiceOverrideForTesting(service) {\n            try KeychainAccessGate.withTaskOverrideForTesting(false) {\n                KeychainCacheStore.setTestStoreForTesting(true)\n                defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n                ClaudeOAuthCredentialsStore.invalidateCache()\n                ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                defer {\n                    ClaudeOAuthCredentialsStore.invalidateCache()\n                    ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                    ClaudeOAuthCredentialsStore._resetClaudeKeychainChangeTrackingForTesting()\n                }\n\n                let tempDir = FileManager.default.temporaryDirectory\n                    .appendingPathComponent(UUID().uuidString, isDirectory: true)\n                try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n                let fileURL = tempDir.appendingPathComponent(\"credentials.json\")\n\n                try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {\n                    ClaudeOAuthCredentialsStore._resetClaudeKeychainChangeTrackingForTesting()\n\n                    let fingerprint = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                        modifiedAt: 1,\n                        createdAt: 1,\n                        persistentRefHash: \"ref1\")\n                    let keychainData = self.makeCredentialsData(\n                        accessToken: \"keychain-token\",\n                        expiresAt: Date(timeIntervalSinceNow: 3600))\n\n                    let creds = try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.onlyOnUserAction) {\n                        try ProviderInteractionContext.$current.withValue(.userInitiated) {\n                            try ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(\n                                data: keychainData,\n                                fingerprint: fingerprint)\n                            {\n                                try ClaudeOAuthCredentialsStore.load(environment: [:], allowKeychainPrompt: false)\n                            }\n                        }\n                    }\n\n                    #expect(creds.accessToken == \"keychain-token\")\n                }\n            }\n        }\n    }\n\n    @Test\n    func `does not show pre alert when claude keychain readable without interaction`() throws {\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n        try KeychainCacheStore.withServiceOverrideForTesting(service) {\n            try KeychainAccessGate.withTaskOverrideForTesting(false) {\n                KeychainCacheStore.setTestStoreForTesting(true)\n                defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n                try ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting {\n                    ClaudeOAuthCredentialsStore.invalidateCache()\n                    ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                    defer {\n                        ClaudeOAuthCredentialsStore.invalidateCache()\n                        ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                    }\n\n                    let tempDir = FileManager.default.temporaryDirectory\n                        .appendingPathComponent(UUID().uuidString, isDirectory: true)\n                    try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n                    let fileURL = tempDir.appendingPathComponent(\"credentials.json\")\n                    try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {\n                        let keychainData = self.makeCredentialsData(\n                            accessToken: \"keychain-token\",\n                            expiresAt: Date(timeIntervalSinceNow: 3600))\n\n                        var preAlertHits = 0\n                        let preflightOverride: (String, String?) -> KeychainAccessPreflight.Outcome = { _, _ in\n                            .allowed\n                        }\n                        let promptHandler: (KeychainPromptContext) -> Void = { _ in\n                            preAlertHits += 1\n                        }\n                        let creds = try KeychainAccessPreflight.withCheckGenericPasswordOverrideForTesting(\n                            preflightOverride,\n                            operation: {\n                                try KeychainPromptHandler.withHandlerForTesting(promptHandler, operation: {\n                                    try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(\n                                        .onlyOnUserAction)\n                                    {\n                                        try ProviderInteractionContext.$current.withValue(.userInitiated) {\n                                            try ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(\n                                                data: keychainData,\n                                                fingerprint: nil)\n                                            {\n                                                try ClaudeOAuthCredentialsStore.load(\n                                                    environment: [:],\n                                                    allowKeychainPrompt: true)\n                                            }\n                                        }\n                                    }\n                                })\n                            })\n\n                        #expect(creds.accessToken == \"keychain-token\")\n                        #expect(preAlertHits == 0)\n                    }\n                }\n            }\n        }\n    }\n\n    @Test\n    func `shows pre alert when claude keychain likely requires interaction`() throws {\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n        try KeychainCacheStore.withServiceOverrideForTesting(service) {\n            try KeychainAccessGate.withTaskOverrideForTesting(false) {\n                KeychainCacheStore.setTestStoreForTesting(true)\n                defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n                try ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting {\n                    ClaudeOAuthCredentialsStore.invalidateCache()\n                    ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                    defer {\n                        ClaudeOAuthCredentialsStore.invalidateCache()\n                        ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                    }\n\n                    let tempDir = FileManager.default.temporaryDirectory\n                        .appendingPathComponent(UUID().uuidString, isDirectory: true)\n                    try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n                    let fileURL = tempDir.appendingPathComponent(\"credentials.json\")\n                    try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {\n                        let keychainData = self.makeCredentialsData(\n                            accessToken: \"keychain-token\",\n                            expiresAt: Date(timeIntervalSinceNow: 3600))\n\n                        var preAlertHits = 0\n                        let preflightOverride: (String, String?) -> KeychainAccessPreflight.Outcome = { _, _ in\n                            .interactionRequired\n                        }\n                        let promptHandler: (KeychainPromptContext) -> Void = { _ in\n                            preAlertHits += 1\n                        }\n                        let creds = try KeychainAccessPreflight.withCheckGenericPasswordOverrideForTesting(\n                            preflightOverride,\n                            operation: {\n                                try KeychainPromptHandler.withHandlerForTesting(promptHandler, operation: {\n                                    try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(\n                                        .onlyOnUserAction)\n                                    {\n                                        try ProviderInteractionContext.$current.withValue(.userInitiated) {\n                                            try ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(\n                                                data: keychainData,\n                                                fingerprint: nil)\n                                            {\n                                                try ClaudeOAuthCredentialsStore.load(\n                                                    environment: [:],\n                                                    allowKeychainPrompt: true)\n                                            }\n                                        }\n                                    }\n                                })\n                            })\n\n                        #expect(creds.accessToken == \"keychain-token\")\n                        // TODO: tighten this to `== 1` once keychain pre-alert delivery is deduplicated/scoped.\n                        // This path can currently emit more than one pre-alert during a single load attempt.\n                        #expect(preAlertHits >= 1)\n                    }\n                }\n            }\n        }\n    }\n\n    @Test\n    func `shows pre alert when claude keychain preflight fails`() throws {\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n        try KeychainCacheStore.withServiceOverrideForTesting(service) {\n            try KeychainAccessGate.withTaskOverrideForTesting(false) {\n                KeychainCacheStore.setTestStoreForTesting(true)\n                defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n                try ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting {\n                    ClaudeOAuthCredentialsStore.invalidateCache()\n                    ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                    defer {\n                        ClaudeOAuthCredentialsStore.invalidateCache()\n                        ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                    }\n\n                    let tempDir = FileManager.default.temporaryDirectory\n                        .appendingPathComponent(UUID().uuidString, isDirectory: true)\n                    try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n                    let fileURL = tempDir.appendingPathComponent(\"credentials.json\")\n                    try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {\n                        let keychainData = self.makeCredentialsData(\n                            accessToken: \"keychain-token\",\n                            expiresAt: Date(timeIntervalSinceNow: 3600))\n\n                        var preAlertHits = 0\n                        let preflightOverride: (String, String?) -> KeychainAccessPreflight.Outcome = { _, _ in\n                            .failure(-1)\n                        }\n                        let promptHandler: (KeychainPromptContext) -> Void = { _ in\n                            preAlertHits += 1\n                        }\n                        let creds = try KeychainAccessPreflight.withCheckGenericPasswordOverrideForTesting(\n                            preflightOverride,\n                            operation: {\n                                try KeychainPromptHandler.withHandlerForTesting(promptHandler, operation: {\n                                    try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(\n                                        .onlyOnUserAction)\n                                    {\n                                        try ProviderInteractionContext.$current.withValue(.userInitiated) {\n                                            try ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(\n                                                data: keychainData,\n                                                fingerprint: nil)\n                                            {\n                                                try ClaudeOAuthCredentialsStore.load(\n                                                    environment: [:],\n                                                    allowKeychainPrompt: true)\n                                            }\n                                        }\n                                    }\n                                })\n                            })\n\n                        #expect(creds.accessToken == \"keychain-token\")\n                        // TODO: tighten this to `== 1` once keychain pre-alert delivery is deduplicated/scoped.\n                        // This path can currently emit more than one pre-alert during a single load attempt.\n                        #expect(preAlertHits >= 1)\n                    }\n                }\n            }\n        }\n    }\n\n    @Test\n    func `experimental reader skips pre alert when security CLI read succeeds`() throws {\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n        try KeychainCacheStore.withServiceOverrideForTesting(service) {\n            try KeychainAccessGate.withTaskOverrideForTesting(false) {\n                KeychainCacheStore.setTestStoreForTesting(true)\n                defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n                try ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting {\n                    ClaudeOAuthCredentialsStore.invalidateCache()\n                    ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                    defer {\n                        ClaudeOAuthCredentialsStore.invalidateCache()\n                        ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                    }\n\n                    let tempDir = FileManager.default.temporaryDirectory\n                        .appendingPathComponent(UUID().uuidString, isDirectory: true)\n                    try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n                    let fileURL = tempDir.appendingPathComponent(\"credentials.json\")\n                    try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {\n                        let securityData = self.makeCredentialsData(\n                            accessToken: \"security-token\",\n                            expiresAt: Date(timeIntervalSinceNow: 3600))\n\n                        var preAlertHits = 0\n                        let preflightOverride: (String, String?) -> KeychainAccessPreflight.Outcome = { _, _ in\n                            .interactionRequired\n                        }\n                        let promptHandler: (KeychainPromptContext) -> Void = { _ in\n                            preAlertHits += 1\n                        }\n                        let creds = try KeychainAccessPreflight.withCheckGenericPasswordOverrideForTesting(\n                            preflightOverride,\n                            operation: {\n                                try KeychainPromptHandler.withHandlerForTesting(promptHandler, operation: {\n                                    try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(\n                                        .securityCLIExperimental)\n                                    {\n                                        try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.always) {\n                                            try ProviderInteractionContext.$current.withValue(.userInitiated) {\n                                                try ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting(\n                                                    .data(securityData))\n                                                {\n                                                    try ClaudeOAuthCredentialsStore.load(\n                                                        environment: [:],\n                                                        allowKeychainPrompt: true)\n                                                }\n                                            }\n                                        }\n                                    }\n                                })\n                            })\n\n                        #expect(creds.accessToken == \"security-token\")\n                        #expect(preAlertHits == 0)\n                    }\n                }\n            }\n        }\n    }\n\n    @Test\n    func `experimental reader shows pre alert when security CLI fails and fallback needs interaction`() throws {\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n        try KeychainCacheStore.withServiceOverrideForTesting(service) {\n            try KeychainAccessGate.withTaskOverrideForTesting(false) {\n                KeychainCacheStore.setTestStoreForTesting(true)\n                defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n                try ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting {\n                    ClaudeOAuthCredentialsStore.invalidateCache()\n                    ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                    defer {\n                        ClaudeOAuthCredentialsStore.invalidateCache()\n                        ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                    }\n\n                    let tempDir = FileManager.default.temporaryDirectory\n                        .appendingPathComponent(UUID().uuidString, isDirectory: true)\n                    try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n                    let fileURL = tempDir.appendingPathComponent(\"credentials.json\")\n                    try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {\n                        let fallbackData = self.makeCredentialsData(\n                            accessToken: \"fallback-token\",\n                            expiresAt: Date(timeIntervalSinceNow: 3600))\n\n                        var preAlertHits = 0\n                        let preflightOverride: (String, String?) -> KeychainAccessPreflight.Outcome = { _, _ in\n                            .interactionRequired\n                        }\n                        let promptHandler: (KeychainPromptContext) -> Void = { _ in\n                            preAlertHits += 1\n                        }\n                        let creds = try KeychainAccessPreflight.withCheckGenericPasswordOverrideForTesting(\n                            preflightOverride,\n                            operation: {\n                                try KeychainPromptHandler.withHandlerForTesting(promptHandler, operation: {\n                                    try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(\n                                        .securityCLIExperimental)\n                                    {\n                                        try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.always) {\n                                            try ProviderInteractionContext.$current.withValue(.userInitiated) {\n                                                try ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(\n                                                    data: fallbackData,\n                                                    fingerprint: nil)\n                                                {\n                                                    try ClaudeOAuthCredentialsStore\n                                                        .withSecurityCLIReadOverrideForTesting(.timedOut) {\n                                                            try ClaudeOAuthCredentialsStore.load(\n                                                                environment: [:],\n                                                                allowKeychainPrompt: true)\n                                                        }\n                                                }\n                                            }\n                                        }\n                                    }\n                                })\n                            })\n\n                        #expect(creds.accessToken == \"fallback-token\")\n                        #expect(preAlertHits >= 1)\n                    }\n                }\n            }\n        }\n    }\n\n    @Test\n    func `experimental reader does not fallback in background when stored mode only on user action`() throws {\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n        try KeychainCacheStore.withServiceOverrideForTesting(service) {\n            try KeychainAccessGate.withTaskOverrideForTesting(false) {\n                KeychainCacheStore.setTestStoreForTesting(true)\n                defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n                try ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting {\n                    ClaudeOAuthCredentialsStore.invalidateCache()\n                    ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                    defer {\n                        ClaudeOAuthCredentialsStore.invalidateCache()\n                        ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                    }\n\n                    let tempDir = FileManager.default.temporaryDirectory\n                        .appendingPathComponent(UUID().uuidString, isDirectory: true)\n                    try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n                    let fileURL = tempDir.appendingPathComponent(\"credentials.json\")\n                    try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {\n                        let fallbackData = self.makeCredentialsData(\n                            accessToken: \"fallback-token\",\n                            expiresAt: Date(timeIntervalSinceNow: 3600))\n\n                        var preAlertHits = 0\n                        let preflightOverride: (String, String?) -> KeychainAccessPreflight.Outcome = { _, _ in\n                            .interactionRequired\n                        }\n                        let promptHandler: (KeychainPromptContext) -> Void = { _ in\n                            preAlertHits += 1\n                        }\n\n                        do {\n                            _ = try KeychainAccessPreflight.withCheckGenericPasswordOverrideForTesting(\n                                preflightOverride,\n                                operation: {\n                                    try KeychainPromptHandler.withHandlerForTesting(promptHandler, operation: {\n                                        try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(\n                                            .securityCLIExperimental)\n                                        {\n                                            try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(\n                                                .onlyOnUserAction)\n                                            {\n                                                try ProviderInteractionContext.$current.withValue(.background) {\n                                                    try ClaudeOAuthCredentialsStore\n                                                        .withClaudeKeychainOverridesForTesting(\n                                                            data: fallbackData,\n                                                            fingerprint: nil)\n                                                        {\n                                                            try ClaudeOAuthCredentialsStore\n                                                                .withSecurityCLIReadOverrideForTesting(.timedOut) {\n                                                                    try ClaudeOAuthCredentialsStore.load(\n                                                                        environment: [:],\n                                                                        allowKeychainPrompt: true,\n                                                                        respectKeychainPromptCooldown: true)\n                                                                }\n                                                        }\n                                                }\n                                            }\n                                        }\n                                    })\n                                })\n                            Issue.record(\"Expected ClaudeOAuthCredentialsError.notFound\")\n                        } catch let error as ClaudeOAuthCredentialsError {\n                            guard case .notFound = error else {\n                                Issue.record(\"Expected .notFound, got \\(error)\")\n                                return\n                            }\n                        }\n\n                        #expect(preAlertHits == 0)\n                    }\n                }\n            }\n        }\n    }\n\n    @Test\n    func `experimental reader does not fallback when stored mode never`() throws {\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n        try KeychainCacheStore.withServiceOverrideForTesting(service) {\n            try KeychainAccessGate.withTaskOverrideForTesting(false) {\n                KeychainCacheStore.setTestStoreForTesting(true)\n                defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n                try ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting {\n                    ClaudeOAuthCredentialsStore.invalidateCache()\n                    ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                    defer {\n                        ClaudeOAuthCredentialsStore.invalidateCache()\n                        ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                    }\n\n                    let tempDir = FileManager.default.temporaryDirectory\n                        .appendingPathComponent(UUID().uuidString, isDirectory: true)\n                    try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n                    let fileURL = tempDir.appendingPathComponent(\"credentials.json\")\n                    try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {\n                        let fallbackData = self.makeCredentialsData(\n                            accessToken: \"fallback-token\",\n                            expiresAt: Date(timeIntervalSinceNow: 3600))\n\n                        var preAlertHits = 0\n                        let preflightOverride: (String, String?) -> KeychainAccessPreflight.Outcome = { _, _ in\n                            .interactionRequired\n                        }\n                        let promptHandler: (KeychainPromptContext) -> Void = { _ in\n                            preAlertHits += 1\n                        }\n\n                        do {\n                            _ = try KeychainAccessPreflight.withCheckGenericPasswordOverrideForTesting(\n                                preflightOverride,\n                                operation: {\n                                    try KeychainPromptHandler.withHandlerForTesting(promptHandler, operation: {\n                                        try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(\n                                            .securityCLIExperimental)\n                                        {\n                                            try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.never) {\n                                                try ProviderInteractionContext.$current.withValue(.userInitiated) {\n                                                    try ClaudeOAuthCredentialsStore\n                                                        .withClaudeKeychainOverridesForTesting(\n                                                            data: fallbackData,\n                                                            fingerprint: nil)\n                                                        {\n                                                            try ClaudeOAuthCredentialsStore\n                                                                .withSecurityCLIReadOverrideForTesting(.timedOut) {\n                                                                    try ClaudeOAuthCredentialsStore.load(\n                                                                        environment: [:],\n                                                                        allowKeychainPrompt: true)\n                                                                }\n                                                        }\n                                                }\n                                            }\n                                        }\n                                    })\n                                })\n                            Issue.record(\"Expected ClaudeOAuthCredentialsError.notFound\")\n                        } catch let error as ClaudeOAuthCredentialsError {\n                            guard case .notFound = error else {\n                                Issue.record(\"Expected .notFound, got \\(error)\")\n                                return\n                            }\n                        }\n\n                        #expect(preAlertHits == 0)\n                    }\n                }\n            }\n        }\n    }\n\n    @Test\n    func `experimental reader non interactive fallback blocked in background when stored mode only on user action`()\n        throws\n    {\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n        try KeychainCacheStore.withServiceOverrideForTesting(service) {\n            try KeychainAccessGate.withTaskOverrideForTesting(false) {\n                KeychainCacheStore.setTestStoreForTesting(true)\n                defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n                try ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting {\n                    ClaudeOAuthCredentialsStore.invalidateCache()\n                    ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                    defer {\n                        ClaudeOAuthCredentialsStore.invalidateCache()\n                        ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                    }\n\n                    let tempDir = FileManager.default.temporaryDirectory\n                        .appendingPathComponent(UUID().uuidString, isDirectory: true)\n                    try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n                    let fileURL = tempDir.appendingPathComponent(\"credentials.json\")\n                    try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {\n                        let fallbackData = self.makeCredentialsData(\n                            accessToken: \"fallback-token-only-on-user-action\",\n                            expiresAt: Date(timeIntervalSinceNow: 3600))\n                        let preflightOverride: (String, String?) -> KeychainAccessPreflight.Outcome = { _, _ in\n                            .allowed\n                        }\n\n                        do {\n                            _ = try KeychainAccessPreflight.withCheckGenericPasswordOverrideForTesting(\n                                preflightOverride,\n                                operation: {\n                                    try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(\n                                        .securityCLIExperimental)\n                                    {\n                                        try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(\n                                            .onlyOnUserAction)\n                                        {\n                                            try ProviderInteractionContext.$current.withValue(.background) {\n                                                try ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(\n                                                    data: fallbackData,\n                                                    fingerprint: nil)\n                                                {\n                                                    try ClaudeOAuthCredentialsStore\n                                                        .withSecurityCLIReadOverrideForTesting(.timedOut) {\n                                                            try ClaudeOAuthCredentialsStore.load(\n                                                                environment: [:],\n                                                                allowKeychainPrompt: false,\n                                                                respectKeychainPromptCooldown: true)\n                                                        }\n                                                }\n                                            }\n                                        }\n                                    }\n                                })\n                            Issue.record(\"Expected ClaudeOAuthCredentialsError.notFound\")\n                        } catch let error as ClaudeOAuthCredentialsError {\n                            guard case .notFound = error else {\n                                Issue.record(\"Expected .notFound, got \\(error)\")\n                                return\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    @Test\n    func `experimental reader allows fallback in background when stored mode always`() throws {\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n        try KeychainCacheStore.withServiceOverrideForTesting(service) {\n            try KeychainAccessGate.withTaskOverrideForTesting(false) {\n                KeychainCacheStore.setTestStoreForTesting(true)\n                defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n                try ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting {\n                    ClaudeOAuthCredentialsStore.invalidateCache()\n                    ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                    defer {\n                        ClaudeOAuthCredentialsStore.invalidateCache()\n                        ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                    }\n\n                    let tempDir = FileManager.default.temporaryDirectory\n                        .appendingPathComponent(UUID().uuidString, isDirectory: true)\n                    try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n                    let fileURL = tempDir.appendingPathComponent(\"credentials.json\")\n                    try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {\n                        let fallbackData = self.makeCredentialsData(\n                            accessToken: \"fallback-token\",\n                            expiresAt: Date(timeIntervalSinceNow: 3600))\n\n                        var preAlertHits = 0\n                        let preflightOverride: (String, String?) -> KeychainAccessPreflight.Outcome = { _, _ in\n                            .interactionRequired\n                        }\n                        let promptHandler: (KeychainPromptContext) -> Void = { _ in\n                            preAlertHits += 1\n                        }\n\n                        let creds = try KeychainAccessPreflight.withCheckGenericPasswordOverrideForTesting(\n                            preflightOverride,\n                            operation: {\n                                try KeychainPromptHandler.withHandlerForTesting(promptHandler, operation: {\n                                    try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(\n                                        .securityCLIExperimental)\n                                    {\n                                        try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.always) {\n                                            try ProviderInteractionContext.$current.withValue(.background) {\n                                                try ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(\n                                                    data: fallbackData,\n                                                    fingerprint: nil)\n                                                {\n                                                    try ClaudeOAuthCredentialsStore\n                                                        .withSecurityCLIReadOverrideForTesting(.timedOut) {\n                                                            try ClaudeOAuthCredentialsStore.load(\n                                                                environment: [:],\n                                                                allowKeychainPrompt: true,\n                                                                respectKeychainPromptCooldown: false)\n                                                        }\n                                                }\n                                            }\n                                        }\n                                    }\n                                })\n                            })\n\n                        #expect(creds.accessToken == \"fallback-token\")\n                        #expect(preAlertHits >= 1)\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/ClaudeOAuthCredentialsStoreSecurityCLIFallbackPolicyTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\n@Suite(.serialized)\nstruct ClaudeOAuthCredentialsStoreSecurityCLIFallbackPolicyTests {\n    private func makeCredentialsData(accessToken: String, expiresAt: Date) -> Data {\n        let millis = Int(expiresAt.timeIntervalSince1970 * 1000)\n        let json = \"\"\"\n        {\n          \"claudeAiOauth\": {\n            \"accessToken\": \"\\(accessToken)\",\n            \"expiresAt\": \\(millis),\n            \"scopes\": [\"user:profile\"]\n          }\n        }\n        \"\"\"\n        return Data(json.utf8)\n    }\n\n    @Test\n    func `experimental reader blocks background fallback per stored policy`() {\n        let fallbackData = self.makeCredentialsData(\n            accessToken: \"fallback-should-be-blocked\",\n            expiresAt: Date(timeIntervalSinceNow: 3600))\n\n        let hasCredentials = KeychainAccessGate.withTaskOverrideForTesting(false) {\n            ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(\n                .securityCLIExperimental,\n                operation: {\n                    ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(\n                        .onlyOnUserAction,\n                        operation: {\n                            ProviderInteractionContext.$current.withValue(.background) {\n                                ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(\n                                    data: fallbackData,\n                                    fingerprint: nil)\n                                {\n                                    ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting(\n                                        .nonZeroExit)\n                                    {\n                                        ClaudeOAuthCredentialsStore.hasClaudeKeychainCredentialsWithoutPrompt()\n                                    }\n                                }\n                            }\n                        })\n                })\n        }\n\n        #expect(hasCredentials == false)\n    }\n\n    @Test\n    func `experimental reader sync from claude keychain without prompt background fallback blocked by stored policy`() {\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n        KeychainCacheStore.withServiceOverrideForTesting(service) {\n            KeychainAccessGate.withTaskOverrideForTesting(false) {\n                KeychainCacheStore.setTestStoreForTesting(true)\n                defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n                ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting {\n                    ClaudeOAuthCredentialsStore.invalidateCache()\n                    defer { ClaudeOAuthCredentialsStore.invalidateCache() }\n\n                    let fallbackData = self.makeCredentialsData(\n                        accessToken: \"sync-fallback-should-be-blocked\",\n                        expiresAt: Date(timeIntervalSinceNow: 3600))\n                    final class Counter: @unchecked Sendable {\n                        var value = 0\n                    }\n                    let preflightCalls = Counter()\n                    let preflightOverride: (String, String?) -> KeychainAccessPreflight.Outcome = { _, _ in\n                        preflightCalls.value += 1\n                        return .allowed\n                    }\n\n                    let synced = KeychainAccessPreflight.withCheckGenericPasswordOverrideForTesting(\n                        preflightOverride,\n                        operation: {\n                            ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(\n                                .securityCLIExperimental,\n                                operation: {\n                                    ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(\n                                        .onlyOnUserAction,\n                                        operation: {\n                                            ProviderInteractionContext.$current.withValue(.background) {\n                                                ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(\n                                                    data: fallbackData,\n                                                    fingerprint: nil)\n                                                {\n                                                    ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting(\n                                                        .timedOut)\n                                                    {\n                                                        ClaudeOAuthCredentialsStore\n                                                            .syncFromClaudeKeychainWithoutPrompt(now: Date())\n                                                    }\n                                                }\n                                            }\n                                        })\n                                })\n                        })\n\n                    #expect(synced == false)\n                    #expect(preflightCalls.value == 0)\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/ClaudeOAuthCredentialsStoreSecurityCLITests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\n@Suite(.serialized)\nstruct ClaudeOAuthCredentialsStoreSecurityCLITests {\n    private func makeCredentialsData(accessToken: String, expiresAt: Date, refreshToken: String? = nil) -> Data {\n        let millis = Int(expiresAt.timeIntervalSince1970 * 1000)\n        let refreshField: String = {\n            guard let refreshToken else { return \"\" }\n            return \",\\n            \\\"refreshToken\\\": \\\"\\(refreshToken)\\\"\"\n        }()\n        let json = \"\"\"\n        {\n          \"claudeAiOauth\": {\n            \"accessToken\": \"\\(accessToken)\",\n            \"expiresAt\": \\(millis),\n            \"scopes\": [\"user:profile\"]\\(refreshField)\n          }\n        }\n        \"\"\"\n        return Data(json.utf8)\n    }\n\n    @Test\n    func `experimental reader prefers security CLI for non interactive load`() throws {\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n        try KeychainCacheStore.withServiceOverrideForTesting(service) {\n            try KeychainAccessGate.withTaskOverrideForTesting(false) {\n                KeychainCacheStore.setTestStoreForTesting(true)\n                defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n                ClaudeOAuthCredentialsStore.invalidateCache()\n                ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                defer {\n                    ClaudeOAuthCredentialsStore.invalidateCache()\n                    ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                    ClaudeOAuthCredentialsStore.setClaudeKeychainDataOverrideForTesting(nil)\n                    ClaudeOAuthCredentialsStore.setClaudeKeychainFingerprintOverrideForTesting(nil)\n                }\n\n                let tempDir = FileManager.default.temporaryDirectory\n                    .appendingPathComponent(UUID().uuidString, isDirectory: true)\n                try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n                let fileURL = tempDir.appendingPathComponent(\"credentials.json\")\n\n                try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {\n                    let securityData = self.makeCredentialsData(\n                        accessToken: \"security-token\",\n                        expiresAt: Date(timeIntervalSinceNow: 3600),\n                        refreshToken: \"security-refresh\")\n\n                    let creds = try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(\n                        .securityCLIExperimental,\n                        operation: {\n                            try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(\n                                .onlyOnUserAction,\n                                operation: {\n                                    try ProviderInteractionContext.$current.withValue(.userInitiated) {\n                                        try ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting(\n                                            .data(securityData))\n                                        {\n                                            try ClaudeOAuthCredentialsStore.load(\n                                                environment: [:],\n                                                allowKeychainPrompt: false)\n                                        }\n                                    }\n                                })\n                        })\n\n                    #expect(creds.accessToken == \"security-token\")\n                    #expect(creds.refreshToken == \"security-refresh\")\n                    #expect(creds.scopes.contains(\"user:profile\"))\n                }\n            }\n        }\n    }\n\n    @Test\n    func `experimental reader non interactive background load still executes security CLI read`() throws {\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n        try KeychainCacheStore.withServiceOverrideForTesting(service) {\n            try KeychainAccessGate.withTaskOverrideForTesting(false) {\n                KeychainCacheStore.setTestStoreForTesting(true)\n                defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n                ClaudeOAuthCredentialsStore.invalidateCache()\n                ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                defer {\n                    ClaudeOAuthCredentialsStore.invalidateCache()\n                    ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                    ClaudeOAuthCredentialsStore.setClaudeKeychainDataOverrideForTesting(nil)\n                    ClaudeOAuthCredentialsStore.setClaudeKeychainFingerprintOverrideForTesting(nil)\n                }\n\n                let tempDir = FileManager.default.temporaryDirectory\n                    .appendingPathComponent(UUID().uuidString, isDirectory: true)\n                try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n                let fileURL = tempDir.appendingPathComponent(\"credentials.json\")\n\n                try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {\n                    let securityData = self.makeCredentialsData(\n                        accessToken: \"security-token-background\",\n                        expiresAt: Date(timeIntervalSinceNow: 3600),\n                        refreshToken: \"security-refresh-background\")\n                    final class ReadCounter: @unchecked Sendable {\n                        var count = 0\n                    }\n                    let securityReadCalls = ReadCounter()\n\n                    let creds = try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(\n                        .securityCLIExperimental,\n                        operation: {\n                            try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(\n                                .onlyOnUserAction,\n                                operation: {\n                                    try ProviderInteractionContext.$current.withValue(.background) {\n                                        try ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting(\n                                            .dynamic { _ in\n                                                securityReadCalls.count += 1\n                                                return securityData\n                                            }) {\n                                                try ClaudeOAuthCredentialsStore.load(\n                                                    environment: [:],\n                                                    allowKeychainPrompt: false)\n                                            }\n                                    }\n                                })\n                        })\n\n                    #expect(creds.accessToken == \"security-token-background\")\n                    #expect(securityReadCalls.count == 1)\n                }\n            }\n        }\n    }\n\n    @Test\n    func `experimental reader falls back when security CLI throws`() throws {\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n        try KeychainCacheStore.withServiceOverrideForTesting(service) {\n            try KeychainAccessGate.withTaskOverrideForTesting(false) {\n                KeychainCacheStore.setTestStoreForTesting(true)\n                defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n                ClaudeOAuthCredentialsStore.invalidateCache()\n                ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                defer {\n                    ClaudeOAuthCredentialsStore.invalidateCache()\n                    ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                    ClaudeOAuthCredentialsStore.setClaudeKeychainDataOverrideForTesting(nil)\n                    ClaudeOAuthCredentialsStore.setClaudeKeychainFingerprintOverrideForTesting(nil)\n                }\n\n                let tempDir = FileManager.default.temporaryDirectory\n                    .appendingPathComponent(UUID().uuidString, isDirectory: true)\n                try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n                let fileURL = tempDir.appendingPathComponent(\"credentials.json\")\n\n                try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {\n                    let fallbackData = self.makeCredentialsData(\n                        accessToken: \"fallback-token\",\n                        expiresAt: Date(timeIntervalSinceNow: 3600),\n                        refreshToken: \"fallback-refresh\")\n\n                    let creds = try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(\n                        .securityCLIExperimental,\n                        operation: {\n                            try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(\n                                .onlyOnUserAction,\n                                operation: {\n                                    try ProviderInteractionContext.$current.withValue(.userInitiated) {\n                                        try ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(\n                                            data: fallbackData,\n                                            fingerprint: nil)\n                                        {\n                                            try ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting(\n                                                .timedOut)\n                                            {\n                                                try ClaudeOAuthCredentialsStore.load(\n                                                    environment: [:],\n                                                    allowKeychainPrompt: false)\n                                            }\n                                        }\n                                    }\n                                })\n                        })\n\n                    #expect(creds.accessToken == \"fallback-token\")\n                }\n            }\n        }\n    }\n\n    @Test\n    func `experimental reader falls back when security CLI output malformed`() throws {\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n        try KeychainCacheStore.withServiceOverrideForTesting(service) {\n            try KeychainAccessGate.withTaskOverrideForTesting(false) {\n                KeychainCacheStore.setTestStoreForTesting(true)\n                defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n                ClaudeOAuthCredentialsStore.invalidateCache()\n                ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                defer {\n                    ClaudeOAuthCredentialsStore.invalidateCache()\n                    ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                    ClaudeOAuthCredentialsStore.setClaudeKeychainDataOverrideForTesting(nil)\n                    ClaudeOAuthCredentialsStore.setClaudeKeychainFingerprintOverrideForTesting(nil)\n                }\n\n                let tempDir = FileManager.default.temporaryDirectory\n                    .appendingPathComponent(UUID().uuidString, isDirectory: true)\n                try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n                let fileURL = tempDir.appendingPathComponent(\"credentials.json\")\n\n                try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {\n                    let fallbackData = self.makeCredentialsData(\n                        accessToken: \"fallback-token\",\n                        expiresAt: Date(timeIntervalSinceNow: 3600))\n\n                    let creds = try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(\n                        .securityCLIExperimental,\n                        operation: {\n                            try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(\n                                .onlyOnUserAction,\n                                operation: {\n                                    try ProviderInteractionContext.$current.withValue(.userInitiated) {\n                                        try ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(\n                                            data: fallbackData,\n                                            fingerprint: nil)\n                                        {\n                                            try ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting(\n                                                .data(Data(\"not-json\".utf8)))\n                                            {\n                                                try ClaudeOAuthCredentialsStore.load(\n                                                    environment: [:],\n                                                    allowKeychainPrompt: false)\n                                            }\n                                        }\n                                    }\n                                })\n                        })\n\n                    #expect(creds.accessToken == \"fallback-token\")\n                }\n            }\n        }\n    }\n\n    @Test\n    func `experimental reader load from claude keychain uses security CLI`() throws {\n        let securityData = self.makeCredentialsData(\n            accessToken: \"security-direct\",\n            expiresAt: Date(timeIntervalSinceNow: 3600),\n            refreshToken: \"security-refresh\")\n        let fingerprintStore = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprintStore()\n        let sentinelFingerprint = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n            modifiedAt: 200,\n            createdAt: 199,\n            persistentRefHash: \"sentinel\")\n\n        let loaded = try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(\n            .securityCLIExperimental,\n            operation: {\n                try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(\n                    .always,\n                    operation: {\n                        try ProviderInteractionContext.$current.withValue(.userInitiated) {\n                            try ClaudeOAuthCredentialsStore.withClaudeKeychainFingerprintStoreOverrideForTesting(\n                                fingerprintStore)\n                            {\n                                try ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(\n                                    data: nil,\n                                    fingerprint: sentinelFingerprint)\n                                {\n                                    try ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting(\n                                        .data(securityData))\n                                    {\n                                        try ClaudeOAuthCredentialsStore.loadFromClaudeKeychain()\n                                    }\n                                }\n                            }\n                        }\n                    })\n            })\n\n        let creds = try ClaudeOAuthCredentials.parse(data: loaded)\n        #expect(creds.accessToken == \"security-direct\")\n        #expect(creds.refreshToken == \"security-refresh\")\n        #expect(fingerprintStore.fingerprint == nil)\n    }\n\n    @Test\n    func `experimental reader has claude keychain credentials without prompt uses security CLI`() {\n        let securityData = self.makeCredentialsData(\n            accessToken: \"security-available\",\n            expiresAt: Date(timeIntervalSinceNow: 3600))\n\n        let hasCredentials = ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(\n            .securityCLIExperimental,\n            operation: {\n                ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(\n                    .always,\n                    operation: {\n                        ProviderInteractionContext.$current.withValue(.userInitiated) {\n                            ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting(\n                                .data(securityData))\n                            {\n                                ClaudeOAuthCredentialsStore.hasClaudeKeychainCredentialsWithoutPrompt()\n                            }\n                        }\n                    })\n            })\n\n        #expect(hasCredentials == true)\n    }\n\n    @Test\n    func `experimental reader has claude keychain credentials without prompt falls back when security CLI fails`() {\n        let fallbackData = self.makeCredentialsData(\n            accessToken: \"fallback-available\",\n            expiresAt: Date(timeIntervalSinceNow: 3600))\n\n        let hasCredentials = ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(\n            .securityCLIExperimental,\n            operation: {\n                ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(\n                    .always,\n                    operation: {\n                        ProviderInteractionContext.$current.withValue(.userInitiated) {\n                            ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(\n                                data: fallbackData,\n                                fingerprint: nil)\n                            {\n                                ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting(\n                                    .nonZeroExit)\n                                {\n                                    ClaudeOAuthCredentialsStore.hasClaudeKeychainCredentialsWithoutPrompt()\n                                }\n                            }\n                        }\n                    })\n            })\n\n        #expect(hasCredentials == true)\n    }\n\n    @Test\n    func `experimental reader ignores prompt policy and cooldown for background silent check`() {\n        let securityData = self.makeCredentialsData(\n            accessToken: \"security-background\",\n            expiresAt: Date(timeIntervalSinceNow: 3600))\n\n        let hasCredentials = KeychainAccessGate.withTaskOverrideForTesting(false) {\n            ClaudeOAuthKeychainAccessGate.withShouldAllowPromptOverrideForTesting(false) {\n                ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(\n                    .securityCLIExperimental,\n                    operation: {\n                        ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(\n                            .never,\n                            operation: {\n                                ProviderInteractionContext.$current.withValue(.background) {\n                                    ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting(\n                                        .data(securityData))\n                                    {\n                                        ClaudeOAuthCredentialsStore.hasClaudeKeychainCredentialsWithoutPrompt()\n                                    }\n                                }\n                            })\n                    })\n            }\n        }\n\n        #expect(hasCredentials == true)\n    }\n\n    @Test\n    func `experimental reader load from claude keychain fallback blocked when stored mode never`() throws {\n        var threwNotFound = false\n        do {\n            _ = try KeychainAccessGate.withTaskOverrideForTesting(false) {\n                try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(\n                    .securityCLIExperimental,\n                    operation: {\n                        try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(\n                            .never,\n                            operation: {\n                                try ProviderInteractionContext.$current.withValue(.background) {\n                                    try ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting(\n                                        .nonZeroExit)\n                                    {\n                                        try ClaudeOAuthCredentialsStore.loadFromClaudeKeychain()\n                                    }\n                                }\n                            })\n                    })\n            }\n        } catch let error as ClaudeOAuthCredentialsError {\n            if case .notFound = error {\n                threwNotFound = true\n            }\n        }\n\n        #expect(threwNotFound == true)\n    }\n\n    @Test\n    func `experimental reader security CLI read pins preferred account when available`() throws {\n        let securityData = self.makeCredentialsData(\n            accessToken: \"security-account-pinned\",\n            expiresAt: Date(timeIntervalSinceNow: 3600))\n        final class AccountBox: @unchecked Sendable {\n            var value: String?\n        }\n        let pinnedAccount = AccountBox()\n\n        let loaded = try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(\n            .securityCLIExperimental,\n            operation: {\n                try ClaudeOAuthCredentialsStore.withSecurityCLIReadAccountOverrideForTesting(\"new-account\") {\n                    try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(\n                        .always,\n                        operation: {\n                            try ProviderInteractionContext.$current.withValue(.userInitiated) {\n                                try ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting(\n                                    .dynamic { request in\n                                        pinnedAccount.value = request.account\n                                        return securityData\n                                    }) {\n                                        try ClaudeOAuthCredentialsStore.loadFromClaudeKeychain()\n                                    }\n                            }\n                        })\n                }\n            })\n\n        let creds = try ClaudeOAuthCredentials.parse(data: loaded)\n        #expect(pinnedAccount.value == \"new-account\")\n        #expect(creds.accessToken == \"security-account-pinned\")\n    }\n\n    @Test\n    func `experimental reader security CLI read does not pin account in background`() throws {\n        let securityData = self.makeCredentialsData(\n            accessToken: \"security-account-not-pinned\",\n            expiresAt: Date(timeIntervalSinceNow: 3600))\n        final class AccountBox: @unchecked Sendable {\n            var value: String?\n        }\n        let pinnedAccount = AccountBox()\n\n        let loaded = try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(\n            .securityCLIExperimental,\n            operation: {\n                try ClaudeOAuthCredentialsStore.withSecurityCLIReadAccountOverrideForTesting(\"new-account\") {\n                    try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(\n                        .always,\n                        operation: {\n                            try ProviderInteractionContext.$current.withValue(.background) {\n                                try ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting(\n                                    .dynamic { request in\n                                        pinnedAccount.value = request.account\n                                        return securityData\n                                    }) {\n                                        try ClaudeOAuthCredentialsStore.loadFromClaudeKeychain()\n                                    }\n                            }\n                        })\n                }\n            })\n\n        let creds = try ClaudeOAuthCredentials.parse(data: loaded)\n        #expect(pinnedAccount.value == nil)\n        #expect(creds.accessToken == \"security-account-not-pinned\")\n    }\n\n    @Test\n    func `experimental reader freshness sync skips security CLI when preflight requires interaction`() throws {\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n        try KeychainCacheStore.withServiceOverrideForTesting(service) {\n            try KeychainAccessGate.withTaskOverrideForTesting(false) {\n                KeychainCacheStore.setTestStoreForTesting(true)\n                defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n                try ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting {\n                    try ClaudeOAuthCredentialsStore.withIsolatedCredentialsFileTrackingForTesting {\n                        ClaudeOAuthCredentialsStore.invalidateCache()\n                        ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                        defer {\n                            ClaudeOAuthCredentialsStore.invalidateCache()\n                            ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                        }\n\n                        let tempDir = FileManager.default.temporaryDirectory\n                            .appendingPathComponent(UUID().uuidString, isDirectory: true)\n                        try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n                        let fileURL = tempDir.appendingPathComponent(\"credentials.json\")\n                        try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {\n                            let securityData = self.makeCredentialsData(\n                                accessToken: \"security-sync\",\n                                expiresAt: Date(timeIntervalSinceNow: 3600))\n                            final class ReadCounter: @unchecked Sendable {\n                                var count = 0\n                            }\n                            let securityReadCalls = ReadCounter()\n\n                            func loadWithPreflight(\n                                _ outcome: KeychainAccessPreflight.Outcome) throws -> ClaudeOAuthCredentials\n                            {\n                                let preflightOverride: (String, String?) -> KeychainAccessPreflight.Outcome = { _, _ in\n                                    outcome\n                                }\n                                return try KeychainAccessPreflight.withCheckGenericPasswordOverrideForTesting(\n                                    preflightOverride,\n                                    operation: {\n                                        try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(\n                                            .securityCLIExperimental)\n                                        {\n                                            try ClaudeOAuthKeychainPromptPreference\n                                                .withTaskOverrideForTesting(.always) {\n                                                    try ProviderInteractionContext.$current.withValue(.background) {\n                                                        try ClaudeOAuthCredentialsStore\n                                                            .withSecurityCLIReadOverrideForTesting(\n                                                                .dynamic { _ in\n                                                                    securityReadCalls.count += 1\n                                                                    return securityData\n                                                                }) {\n                                                                    try ClaudeOAuthCredentialsStore.load(\n                                                                        environment: [:],\n                                                                        allowKeychainPrompt: false,\n                                                                        respectKeychainPromptCooldown: true)\n                                                            }\n                                                    }\n                                                }\n                                        }\n                                    })\n                            }\n\n                            let first = try loadWithPreflight(.allowed)\n                            #expect(first.accessToken == \"security-sync\")\n                            #expect(securityReadCalls.count == 1)\n\n                            let second = try loadWithPreflight(.interactionRequired)\n                            #expect(second.accessToken == \"security-sync\")\n                            #expect(securityReadCalls.count == 1)\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    @Test\n    func `experimental reader freshness sync background respects stored only on user action`() throws {\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n        try KeychainCacheStore.withServiceOverrideForTesting(service) {\n            try KeychainAccessGate.withTaskOverrideForTesting(false) {\n                KeychainCacheStore.setTestStoreForTesting(true)\n                defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n                try ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting {\n                    try ClaudeOAuthCredentialsStore.withIsolatedCredentialsFileTrackingForTesting {\n                        ClaudeOAuthCredentialsStore.invalidateCache()\n                        ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                        defer {\n                            ClaudeOAuthCredentialsStore.invalidateCache()\n                            ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                        }\n\n                        let tempDir = FileManager.default.temporaryDirectory\n                            .appendingPathComponent(UUID().uuidString, isDirectory: true)\n                        try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n                        let fileURL = tempDir.appendingPathComponent(\"credentials.json\")\n                        try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {\n                            let securityData = self.makeCredentialsData(\n                                accessToken: \"security-sync-only-on-user-action\",\n                                expiresAt: Date(timeIntervalSinceNow: 3600))\n                            final class ReadCounter: @unchecked Sendable {\n                                var count = 0\n                            }\n                            let securityReadCalls = ReadCounter()\n                            let preflightOverride: (String, String?) -> KeychainAccessPreflight.Outcome = { _, _ in\n                                .allowed\n                            }\n\n                            func load(_ interaction: ProviderInteraction) throws -> ClaudeOAuthCredentials {\n                                try KeychainAccessPreflight.withCheckGenericPasswordOverrideForTesting(\n                                    preflightOverride,\n                                    operation: {\n                                        try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(\n                                            .securityCLIExperimental)\n                                        {\n                                            try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(\n                                                .onlyOnUserAction)\n                                            {\n                                                try ProviderInteractionContext.$current.withValue(interaction) {\n                                                    try ClaudeOAuthCredentialsStore\n                                                        .withSecurityCLIReadOverrideForTesting(\n                                                            .dynamic { _ in\n                                                                securityReadCalls.count += 1\n                                                                return securityData\n                                                            }) {\n                                                                try ClaudeOAuthCredentialsStore.load(\n                                                                    environment: [:],\n                                                                    allowKeychainPrompt: false,\n                                                                    respectKeychainPromptCooldown: true)\n                                                        }\n                                                }\n                                            }\n                                        }\n                                    })\n                            }\n\n                            let first = try load(.userInitiated)\n                            #expect(first.accessToken == \"security-sync-only-on-user-action\")\n                            #expect(securityReadCalls.count == 1)\n\n                            let second = try load(.background)\n                            #expect(second.accessToken == \"security-sync-only-on-user-action\")\n                            #expect(securityReadCalls.count == 1)\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    @Test\n    func `experimental reader sync skips fingerprint probe after security CLI read`() {\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n        KeychainCacheStore.withServiceOverrideForTesting(service) {\n            KeychainAccessGate.withTaskOverrideForTesting(false) {\n                KeychainCacheStore.setTestStoreForTesting(true)\n                defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n                ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting {\n                    ClaudeOAuthCredentialsStore.invalidateCache()\n                    defer { ClaudeOAuthCredentialsStore.invalidateCache() }\n\n                    let securityData = self.makeCredentialsData(\n                        accessToken: \"security-sync-no-fingerprint-probe\",\n                        expiresAt: Date(timeIntervalSinceNow: 3600))\n                    let fingerprintStore = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprintStore()\n                    let sentinelFingerprint = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                        modifiedAt: 123,\n                        createdAt: 122,\n                        persistentRefHash: \"sentinel\")\n\n                    let synced = ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(\n                        .securityCLIExperimental,\n                        operation: {\n                            ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.always) {\n                                ProviderInteractionContext.$current.withValue(.background) {\n                                    ClaudeOAuthCredentialsStore.withClaudeKeychainFingerprintStoreOverrideForTesting(\n                                        fingerprintStore)\n                                    {\n                                        ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(\n                                            data: nil,\n                                            fingerprint: sentinelFingerprint)\n                                        {\n                                            ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting(\n                                                .data(securityData))\n                                            {\n                                                ClaudeOAuthCredentialsStore.syncFromClaudeKeychainWithoutPrompt(\n                                                    now: Date())\n                                            }\n                                        }\n                                    }\n                                }\n                            }\n                        })\n\n                    #expect(synced == true)\n                    #expect(fingerprintStore.fingerprint == nil)\n                }\n            }\n        }\n    }\n\n    @Test\n    func `experimental reader no prompt repair skips fingerprint probe after security CLI success`() throws {\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n        try KeychainCacheStore.withServiceOverrideForTesting(service) {\n            try KeychainAccessGate.withTaskOverrideForTesting(false) {\n                KeychainCacheStore.setTestStoreForTesting(true)\n                defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n                ClaudeOAuthCredentialsStore.invalidateCache()\n                ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                defer {\n                    ClaudeOAuthCredentialsStore.invalidateCache()\n                    ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                }\n\n                let tempDir = FileManager.default.temporaryDirectory\n                    .appendingPathComponent(UUID().uuidString, isDirectory: true)\n                try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n                let fileURL = tempDir.appendingPathComponent(\"credentials.json\")\n                try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {\n                    let securityData = self.makeCredentialsData(\n                        accessToken: \"security-repair-no-fingerprint-probe\",\n                        expiresAt: Date(timeIntervalSinceNow: 3600))\n                    let fingerprintStore = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprintStore()\n                    let sentinelFingerprint = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                        modifiedAt: 456,\n                        createdAt: 455,\n                        persistentRefHash: \"sentinel\")\n\n                    let record = try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(\n                        .securityCLIExperimental,\n                        operation: {\n                            try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.always) {\n                                try ProviderInteractionContext.$current.withValue(.background) {\n                                    try ClaudeOAuthCredentialsStore\n                                        .withClaudeKeychainFingerprintStoreOverrideForTesting(\n                                            fingerprintStore)\n                                        {\n                                            try ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(\n                                                data: nil,\n                                                fingerprint: sentinelFingerprint)\n                                            {\n                                                try ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting(\n                                                    .data(securityData))\n                                                {\n                                                    try ClaudeOAuthCredentialsStore.loadRecord(\n                                                        environment: [:],\n                                                        allowKeychainPrompt: false,\n                                                        respectKeychainPromptCooldown: true)\n                                                }\n                                            }\n                                        }\n                                }\n                            }\n                        })\n\n                    #expect(record.credentials.accessToken == \"security-repair-no-fingerprint-probe\")\n                    #expect(record.source == .claudeKeychain)\n                    #expect(fingerprintStore.fingerprint == nil)\n                }\n            }\n        }\n    }\n\n    @Test\n    func `experimental reader load with prompt skips fingerprint probe after security CLI success`() throws {\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n        try KeychainCacheStore.withServiceOverrideForTesting(service) {\n            try KeychainAccessGate.withTaskOverrideForTesting(false) {\n                KeychainCacheStore.setTestStoreForTesting(true)\n                defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n                ClaudeOAuthCredentialsStore.invalidateCache()\n                ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                defer {\n                    ClaudeOAuthCredentialsStore.invalidateCache()\n                    ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                }\n\n                let tempDir = FileManager.default.temporaryDirectory\n                    .appendingPathComponent(UUID().uuidString, isDirectory: true)\n                try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n                let fileURL = tempDir.appendingPathComponent(\"credentials.json\")\n                try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {\n                    let securityData = self.makeCredentialsData(\n                        accessToken: \"security-load-with-prompt\",\n                        expiresAt: Date(timeIntervalSinceNow: 3600))\n                    let fingerprintStore = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprintStore()\n                    let sentinelFingerprint = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                        modifiedAt: 321,\n                        createdAt: 320,\n                        persistentRefHash: \"sentinel\")\n\n                    let creds = try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(\n                        .securityCLIExperimental,\n                        operation: {\n                            try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.always) {\n                                try ProviderInteractionContext.$current.withValue(.userInitiated) {\n                                    try ClaudeOAuthCredentialsStore\n                                        .withClaudeKeychainFingerprintStoreOverrideForTesting(\n                                            fingerprintStore)\n                                        {\n                                            try ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(\n                                                data: nil,\n                                                fingerprint: sentinelFingerprint)\n                                            {\n                                                try ClaudeOAuthCredentialsStore\n                                                    .withSecurityCLIReadOverrideForTesting(\n                                                        .data(securityData))\n                                                    {\n                                                        try ClaudeOAuthCredentialsStore.load(\n                                                            environment: [:],\n                                                            allowKeychainPrompt: true,\n                                                            respectKeychainPromptCooldown: false)\n                                                    }\n                                            }\n                                        }\n                                }\n                            }\n                        })\n\n                    #expect(creds.accessToken == \"security-load-with-prompt\")\n                    #expect(fingerprintStore.fingerprint == nil)\n                }\n            }\n        }\n    }\n\n    @Test\n    func `experimental reader load with prompt does not read when global keychain disabled`() throws {\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n        try KeychainCacheStore.withServiceOverrideForTesting(service) {\n            try KeychainAccessGate.withTaskOverrideForTesting(true) {\n                KeychainCacheStore.setTestStoreForTesting(true)\n                defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n                ClaudeOAuthCredentialsStore.invalidateCache()\n                ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                defer {\n                    ClaudeOAuthCredentialsStore.invalidateCache()\n                    ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                }\n\n                let tempDir = FileManager.default.temporaryDirectory\n                    .appendingPathComponent(UUID().uuidString, isDirectory: true)\n                try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n                let fileURL = tempDir.appendingPathComponent(\"credentials.json\")\n                try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {\n                    let securityData = self.makeCredentialsData(\n                        accessToken: \"security-should-not-read\",\n                        expiresAt: Date(timeIntervalSinceNow: 3600))\n                    var threwNotFound = false\n                    final class ReadCounter: @unchecked Sendable {\n                        var count = 0\n                    }\n                    let securityReadCalls = ReadCounter()\n\n                    do {\n                        _ = try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(\n                            .securityCLIExperimental,\n                            operation: {\n                                try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.always) {\n                                    try ProviderInteractionContext.$current.withValue(.userInitiated) {\n                                        try ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting(\n                                            .dynamic { _ in\n                                                securityReadCalls.count += 1\n                                                return securityData\n                                            }) {\n                                                try ClaudeOAuthCredentialsStore.load(\n                                                    environment: [:],\n                                                    allowKeychainPrompt: true,\n                                                    respectKeychainPromptCooldown: false)\n                                            }\n                                    }\n                                }\n                            })\n                    } catch let error as ClaudeOAuthCredentialsError {\n                        if case .notFound = error {\n                            threwNotFound = true\n                        } else {\n                            throw error\n                        }\n                    }\n\n                    #expect(threwNotFound == true)\n                    #expect(securityReadCalls.count < 1)\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/ClaudeOAuthCredentialsStoreTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\n@Suite(.serialized)\nstruct ClaudeOAuthCredentialsStoreTests {\n    private func makeCredentialsData(accessToken: String, expiresAt: Date, refreshToken: String? = nil) -> Data {\n        let millis = Int(expiresAt.timeIntervalSince1970 * 1000)\n        let refreshField: String = {\n            guard let refreshToken else { return \"\" }\n            return \",\\n            \\\"refreshToken\\\": \\\"\\(refreshToken)\\\"\"\n        }()\n        let json = \"\"\"\n        {\n          \"claudeAiOauth\": {\n            \"accessToken\": \"\\(accessToken)\",\n            \"expiresAt\": \\(millis),\n            \"scopes\": [\"user:profile\"]\\(refreshField)\n          }\n        }\n        \"\"\"\n        return Data(json.utf8)\n    }\n\n    @Test\n    func `loads from keychain cache before expired file`() throws {\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n        try ProviderInteractionContext.$current.withValue(.background) {\n            try KeychainCacheStore.withServiceOverrideForTesting(service) {\n                try KeychainAccessGate.withTaskOverrideForTesting(false) {\n                    KeychainCacheStore.setTestStoreForTesting(true)\n                    defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n                    ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                    defer { ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting() }\n                    try ClaudeOAuthCredentialsStore.withIsolatedCredentialsFileTrackingForTesting {\n                        try ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting {\n                            let tempDir = FileManager.default.temporaryDirectory\n                                .appendingPathComponent(UUID().uuidString, isDirectory: true)\n                            try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n                            let fileURL = tempDir.appendingPathComponent(\"credentials.json\")\n                            try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {\n                                let expiredData = self.makeCredentialsData(\n                                    accessToken: \"expired\",\n                                    expiresAt: Date(timeIntervalSinceNow: -3600))\n                                try expiredData.write(to: fileURL)\n\n                                let cachedData = self.makeCredentialsData(\n                                    accessToken: \"cached\",\n                                    expiresAt: Date(timeIntervalSinceNow: 3600))\n                                let cacheEntry = ClaudeOAuthCredentialsStore.CacheEntry(\n                                    data: cachedData,\n                                    storedAt: Date())\n                                let cacheKey = KeychainCacheStore.Key.oauth(provider: .claude)\n                                ClaudeOAuthCredentialsStore.invalidateCache()\n                                KeychainCacheStore.store(key: cacheKey, entry: cacheEntry)\n                                defer { KeychainCacheStore.clear(key: cacheKey) }\n                                _ = try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(\n                                    .securityFramework)\n                                {\n                                    try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(\n                                        .onlyOnUserAction)\n                                    {\n                                        try ClaudeOAuthCredentialsStore.load(\n                                            environment: [:],\n                                            allowKeychainPrompt: false)\n                                    }\n                                }\n                                // Re-store to cache after file check has marked file as \"seen\"\n                                KeychainCacheStore.store(key: cacheKey, entry: cacheEntry)\n                                let creds = try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(\n                                    .securityFramework)\n                                {\n                                    try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(\n                                        .onlyOnUserAction)\n                                    {\n                                        try ClaudeOAuthCredentialsStore.load(\n                                            environment: [:],\n                                            allowKeychainPrompt: false)\n                                    }\n                                }\n\n                                #expect(creds.accessToken == \"cached\")\n                                #expect(creds.isExpired == false)\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    @Test\n    func `load record non interactive repair can be disabled`() throws {\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n        try KeychainCacheStore.withServiceOverrideForTesting(service) {\n            KeychainCacheStore.setTestStoreForTesting(true)\n            defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n            ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n            defer { ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting() }\n\n            try ClaudeOAuthCredentialsStore.withIsolatedCredentialsFileTrackingForTesting {\n                try ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting {\n                    // Ensure file-based lookup doesn't interfere (and avoid touching ~/.claude).\n                    let tempDir = FileManager.default.temporaryDirectory\n                        .appendingPathComponent(UUID().uuidString, isDirectory: true)\n                    try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n                    let fileURL = tempDir.appendingPathComponent(\"credentials.json\")\n                    try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {\n                        ClaudeOAuthCredentialsStore.invalidateCache()\n\n                        let keychainData = self.makeCredentialsData(\n                            accessToken: \"claude-keychain\",\n                            expiresAt: Date(timeIntervalSinceNow: 3600))\n\n                        // Simulate Claude Keychain containing creds, without querying the real Keychain.\n                        try ProviderInteractionContext.$current.withValue(.userInitiated) {\n                            try ClaudeOAuthCredentialsStore\n                                .withClaudeKeychainOverridesForTesting(data: keychainData, fingerprint: nil) {\n                                    // When repair is disabled, non-interactive loads should not consult Claude's\n                                    // keychain data.\n                                    do {\n                                        _ = try ClaudeOAuthCredentialsStore.loadRecord(\n                                            environment: [:],\n                                            allowKeychainPrompt: false,\n                                            respectKeychainPromptCooldown: true,\n                                            allowClaudeKeychainRepairWithoutPrompt: false)\n                                        Issue.record(\"Expected ClaudeOAuthCredentialsError.notFound\")\n                                    } catch let error as ClaudeOAuthCredentialsError {\n                                        guard case .notFound = error else {\n                                            Issue.record(\"Expected .notFound, got \\(error)\")\n                                            return\n                                        }\n                                    }\n\n                                    // With repair enabled, we should be able to seed from the \"Claude keychain\"\n                                    // override.\n                                    let record = try ClaudeOAuthCredentialsStore.loadRecord(\n                                        environment: [:],\n                                        allowKeychainPrompt: false,\n                                        respectKeychainPromptCooldown: true,\n                                        allowClaudeKeychainRepairWithoutPrompt: true)\n                                    #expect(record.credentials.accessToken == \"claude-keychain\")\n                                }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    @Test\n    func `invalidates cache when credentials file changes`() throws {\n        KeychainCacheStore.setTestStoreForTesting(true)\n        defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n        ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n        defer { ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting() }\n\n        // Avoid interacting with the real Keychain in unit tests.\n        try ClaudeOAuthCredentialsStore.withKeychainAccessOverrideForTesting(true) {\n            let tempDir = FileManager.default.temporaryDirectory\n                .appendingPathComponent(UUID().uuidString, isDirectory: true)\n            try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n            let fileURL = tempDir.appendingPathComponent(\"credentials.json\")\n            try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {\n                let first = self.makeCredentialsData(\n                    accessToken: \"first\",\n                    expiresAt: Date(timeIntervalSinceNow: 3600))\n                try first.write(to: fileURL)\n\n                let cacheKey = KeychainCacheStore.Key.oauth(provider: .claude)\n                let cacheEntry = ClaudeOAuthCredentialsStore.CacheEntry(data: first, storedAt: Date())\n                KeychainCacheStore.store(key: cacheKey, entry: cacheEntry)\n\n                _ = try ClaudeOAuthCredentialsStore.load(environment: [:])\n\n                let updated = self.makeCredentialsData(\n                    accessToken: \"second\",\n                    expiresAt: Date(timeIntervalSinceNow: 3600))\n                try updated.write(to: fileURL)\n\n                #expect(ClaudeOAuthCredentialsStore.invalidateCacheIfCredentialsFileChanged())\n                KeychainCacheStore.clear(key: cacheKey)\n\n                let creds = try ClaudeOAuthCredentialsStore.load(environment: [:])\n                #expect(creds.accessToken == \"second\")\n            }\n        }\n    }\n\n    @Test\n    func `returns expired file when no other sources`() throws {\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n        try KeychainCacheStore.withServiceOverrideForTesting(service) {\n            try KeychainAccessGate.withTaskOverrideForTesting(true) {\n                KeychainCacheStore.setTestStoreForTesting(true)\n                defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n                try ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting {\n                    ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                    defer { ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting() }\n\n                    let tempDir = FileManager.default.temporaryDirectory\n                        .appendingPathComponent(UUID().uuidString, isDirectory: true)\n                    try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n                    let fileURL = tempDir.appendingPathComponent(\"credentials.json\")\n                    try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {\n                        let expiredData = self.makeCredentialsData(\n                            accessToken: \"expired-only\",\n                            expiresAt: Date(timeIntervalSinceNow: -3600))\n                        try expiredData.write(to: fileURL)\n\n                        try ClaudeOAuthCredentialsStore.withKeychainAccessOverrideForTesting(true) {\n                            ClaudeOAuthCredentialsStore.invalidateCache()\n                            let creds = try ClaudeOAuthCredentialsStore.load(environment: [:])\n\n                            #expect(creds.accessToken == \"expired-only\")\n                            #expect(creds.isExpired == true)\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    @Test\n    func `load with auto refresh expired claude CLI owner throws delegated refresh`() async throws {\n        KeychainCacheStore.setTestStoreForTesting(true)\n        defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n        ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n        defer { ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting() }\n\n        let tempDir = FileManager.default.temporaryDirectory\n            .appendingPathComponent(UUID().uuidString, isDirectory: true)\n        try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n        let fileURL = tempDir.appendingPathComponent(\"credentials.json\")\n        await ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {\n            await ClaudeOAuthCredentialsStore.withKeychainAccessOverrideForTesting(true) {\n                ClaudeOAuthCredentialsStore.invalidateCache()\n                let cacheKey = KeychainCacheStore.Key.oauth(provider: .claude)\n                defer { KeychainCacheStore.clear(key: cacheKey) }\n\n                let expiredData = self.makeCredentialsData(\n                    accessToken: \"expired-claude-cli-owner\",\n                    expiresAt: Date(timeIntervalSinceNow: -3600),\n                    refreshToken: \"refresh-token\")\n                KeychainCacheStore.store(\n                    key: cacheKey,\n                    entry: ClaudeOAuthCredentialsStore.CacheEntry(\n                        data: expiredData,\n                        storedAt: Date(),\n                        owner: .claudeCLI))\n\n                do {\n                    _ = try await ClaudeOAuthCredentialsStore.loadWithAutoRefresh(\n                        environment: [:],\n                        allowKeychainPrompt: false,\n                        respectKeychainPromptCooldown: true)\n                    Issue.record(\"Expected delegated refresh error for Claude CLI-owned credentials\")\n                } catch let error as ClaudeOAuthCredentialsError {\n                    guard case .refreshDelegatedToClaudeCLI = error else {\n                        Issue.record(\"Expected .refreshDelegatedToClaudeCLI, got \\(error)\")\n                        return\n                    }\n                } catch {\n                    Issue.record(\"Expected ClaudeOAuthCredentialsError, got \\(error)\")\n                }\n            }\n        }\n    }\n\n    @Test\n    func `load with auto refresh expired codexbar owner uses direct refresh path`() async throws {\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n        try await KeychainCacheStore.withServiceOverrideForTesting(service) {\n            KeychainCacheStore.setTestStoreForTesting(true)\n            defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n            ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n            defer { ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting() }\n\n            let tempDir = FileManager.default.temporaryDirectory\n                .appendingPathComponent(UUID().uuidString, isDirectory: true)\n            try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n            let fileURL = tempDir.appendingPathComponent(\"credentials.json\")\n            await ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {\n                await ClaudeOAuthCredentialsStore.withKeychainAccessOverrideForTesting(true) {\n                    ClaudeOAuthCredentialsStore.invalidateCache()\n                    let cacheKey = KeychainCacheStore.Key.oauth(provider: .claude)\n                    defer { KeychainCacheStore.clear(key: cacheKey) }\n\n                    let expiredData = self.makeCredentialsData(\n                        accessToken: \"expired-codexbar-owner\",\n                        expiresAt: Date(timeIntervalSinceNow: -3600),\n                        refreshToken: \"refresh-token\")\n                    KeychainCacheStore.store(\n                        key: cacheKey,\n                        entry: ClaudeOAuthCredentialsStore.CacheEntry(\n                            data: expiredData,\n                            storedAt: Date(),\n                            owner: .codexbar))\n\n                    await ClaudeOAuthRefreshFailureGate.$shouldAttemptOverride.withValue(false) {\n                        do {\n                            _ = try await ClaudeOAuthCredentialsStore.loadWithAutoRefresh(\n                                environment: [:],\n                                allowKeychainPrompt: false,\n                                respectKeychainPromptCooldown: true)\n                            Issue.record(\"Expected refresh failure for CodexBar-owned direct refresh path\")\n                        } catch let error as ClaudeOAuthCredentialsError {\n                            guard case .refreshFailed = error else {\n                                Issue.record(\"Expected .refreshFailed, got \\(error)\")\n                                return\n                            }\n                        } catch {\n                            Issue.record(\"Expected ClaudeOAuthCredentialsError, got \\(error)\")\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    @Test\n    func `load record legacy cache entry without owner defaults to claude CLI owner`() throws {\n        KeychainCacheStore.setTestStoreForTesting(true)\n        defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n        ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n        defer { ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting() }\n\n        let tempDir = FileManager.default.temporaryDirectory\n            .appendingPathComponent(UUID().uuidString, isDirectory: true)\n        try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n        let fileURL = tempDir.appendingPathComponent(\"credentials.json\")\n        try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {\n            try ClaudeOAuthCredentialsStore.withKeychainAccessOverrideForTesting(true) {\n                ClaudeOAuthCredentialsStore.invalidateCache()\n                let cacheKey = KeychainCacheStore.Key.oauth(provider: .claude)\n                defer { KeychainCacheStore.clear(key: cacheKey) }\n\n                let validData = self.makeCredentialsData(\n                    accessToken: \"legacy-owner\",\n                    expiresAt: Date(timeIntervalSinceNow: 3600),\n                    refreshToken: \"refresh-token\")\n                KeychainCacheStore.store(\n                    key: cacheKey,\n                    entry: ClaudeOAuthCredentialsStore.CacheEntry(\n                        data: validData,\n                        storedAt: Date()))\n\n                let record = try ClaudeOAuthCredentialsStore.loadRecord(\n                    environment: [:],\n                    allowKeychainPrompt: false,\n                    respectKeychainPromptCooldown: true)\n                #expect(record.owner == .claudeCLI)\n                #expect(record.source == .cacheKeychain)\n            }\n        }\n    }\n\n    @Test\n    func `has cached credentials returns false for expired unrefreshable codexbar cache entry`() throws {\n        KeychainCacheStore.setTestStoreForTesting(true)\n        defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n        ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n        defer { ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting() }\n\n        let tempDir = FileManager.default.temporaryDirectory\n            .appendingPathComponent(UUID().uuidString, isDirectory: true)\n        try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n        let fileURL = tempDir.appendingPathComponent(\"credentials.json\")\n        ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {\n            ClaudeOAuthCredentialsStore.invalidateCache()\n\n            let expiredData = self.makeCredentialsData(\n                accessToken: \"expired-no-refresh\",\n                expiresAt: Date(timeIntervalSinceNow: -3600),\n                refreshToken: nil)\n            let cacheEntry = ClaudeOAuthCredentialsStore.CacheEntry(\n                data: expiredData,\n                storedAt: Date(),\n                owner: .codexbar)\n            let cacheKey = KeychainCacheStore.Key.oauth(provider: .claude)\n            KeychainCacheStore.store(key: cacheKey, entry: cacheEntry)\n\n            #expect(ClaudeOAuthCredentialsStore.hasCachedCredentials() == false)\n        }\n    }\n\n    @Test\n    func `has cached credentials returns true for expired refreshable cache entry`() throws {\n        KeychainCacheStore.setTestStoreForTesting(true)\n        defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n        ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n        defer { ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting() }\n\n        let tempDir = FileManager.default.temporaryDirectory\n            .appendingPathComponent(UUID().uuidString, isDirectory: true)\n        try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n        let fileURL = tempDir.appendingPathComponent(\"credentials.json\")\n        ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {\n            ClaudeOAuthCredentialsStore.invalidateCache()\n\n            let expiredData = self.makeCredentialsData(\n                accessToken: \"expired-refreshable\",\n                expiresAt: Date(timeIntervalSinceNow: -3600),\n                refreshToken: \"refresh\")\n            let cacheEntry = ClaudeOAuthCredentialsStore.CacheEntry(data: expiredData, storedAt: Date())\n            let cacheKey = KeychainCacheStore.Key.oauth(provider: .claude)\n            KeychainCacheStore.store(key: cacheKey, entry: cacheEntry)\n\n            #expect(ClaudeOAuthCredentialsStore.hasCachedCredentials() == true)\n        }\n    }\n\n    @Test\n    func `has cached credentials returns true for expired claude CLI backed credentials file`() throws {\n        KeychainCacheStore.setTestStoreForTesting(true)\n        defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n        ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n        defer { ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting() }\n\n        let tempDir = FileManager.default.temporaryDirectory\n            .appendingPathComponent(UUID().uuidString, isDirectory: true)\n        try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n        let fileURL = tempDir.appendingPathComponent(\"credentials.json\")\n        try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {\n            ClaudeOAuthCredentialsStore.invalidateCache()\n\n            let expiredData = self.makeCredentialsData(\n                accessToken: \"expired-file-no-refresh\",\n                expiresAt: Date(timeIntervalSinceNow: -3600),\n                refreshToken: nil)\n            try expiredData.write(to: fileURL)\n\n            #expect(ClaudeOAuthCredentialsStore.hasCachedCredentials() == true)\n        }\n    }\n\n    @Test\n    func `syncs cache when claude keychain fingerprint changes and token differs`() throws {\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n        try KeychainCacheStore.withServiceOverrideForTesting(service) {\n            try KeychainAccessGate.withTaskOverrideForTesting(false) {\n                KeychainCacheStore.setTestStoreForTesting(true)\n                defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n                ClaudeOAuthKeychainAccessGate.resetForTesting()\n                defer { ClaudeOAuthKeychainAccessGate.resetForTesting() }\n\n                ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                defer { ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting() }\n\n                let tempDir = FileManager.default.temporaryDirectory\n                    .appendingPathComponent(UUID().uuidString, isDirectory: true)\n                try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n                let fileURL = tempDir.appendingPathComponent(\"credentials.json\")\n                try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {\n                    ClaudeOAuthCredentialsStore.invalidateCache()\n                    ClaudeOAuthCredentialsStore._resetClaudeKeychainChangeTrackingForTesting()\n                    defer {\n                        ClaudeOAuthCredentialsStore.invalidateCache()\n                        ClaudeOAuthCredentialsStore._resetClaudeKeychainChangeTrackingForTesting()\n                        ClaudeOAuthCredentialsStore.setClaudeKeychainDataOverrideForTesting(nil)\n                        ClaudeOAuthCredentialsStore.setClaudeKeychainFingerprintOverrideForTesting(nil)\n                    }\n\n                    // Avoid cross-suite interference from UserDefaults fingerprint persistence.\n                    let fingerprintStore = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprintStore()\n\n                    let cacheKey = KeychainCacheStore.Key.oauth(provider: .claude)\n                    let cachedData = self.makeCredentialsData(\n                        accessToken: \"cached-token\",\n                        expiresAt: Date(timeIntervalSinceNow: 3600))\n                    KeychainCacheStore.store(\n                        key: cacheKey,\n                        entry: ClaudeOAuthCredentialsStore.CacheEntry(data: cachedData, storedAt: Date()))\n\n                    let fingerprint1 = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                        modifiedAt: 1,\n                        createdAt: 1,\n                        persistentRefHash: \"ref1\")\n\n                    let first = try ProviderInteractionContext.$current.withValue(.userInitiated) {\n                        try ClaudeOAuthCredentialsStore.withClaudeKeychainFingerprintStoreOverrideForTesting(\n                            fingerprintStore)\n                        {\n                            try ClaudeOAuthKeychainAccessGate.withShouldAllowPromptOverrideForTesting(true) {\n                                try ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(\n                                    data: cachedData,\n                                    fingerprint: fingerprint1)\n                                {\n                                    try ClaudeOAuthCredentialsStore.load(environment: [:], allowKeychainPrompt: false)\n                                }\n                            }\n                        }\n                    }\n                    #expect(first.accessToken == \"cached-token\")\n                    #expect(fingerprintStore.fingerprint == fingerprint1)\n\n                    ClaudeOAuthCredentialsStore._resetClaudeKeychainChangeThrottleForTesting()\n\n                    let fingerprint2 = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                        modifiedAt: 2,\n                        createdAt: 2,\n                        persistentRefHash: \"ref2\")\n\n                    let keychainData = self.makeCredentialsData(\n                        accessToken: \"keychain-token\",\n                        expiresAt: Date(timeIntervalSinceNow: 3600))\n\n                    let second = try ProviderInteractionContext.$current.withValue(.userInitiated) {\n                        try ClaudeOAuthCredentialsStore.withClaudeKeychainFingerprintStoreOverrideForTesting(\n                            fingerprintStore)\n                        {\n                            try ClaudeOAuthKeychainAccessGate.withShouldAllowPromptOverrideForTesting(true) {\n                                try ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(\n                                    data: keychainData,\n                                    fingerprint: fingerprint2)\n                                {\n                                    try ClaudeOAuthCredentialsStore.load(environment: [:], allowKeychainPrompt: false)\n                                }\n                            }\n                        }\n                    }\n                    #expect(second.accessToken == \"keychain-token\")\n                    #expect(fingerprintStore.fingerprint == fingerprint2)\n\n                    switch KeychainCacheStore.load(key: cacheKey, as: ClaudeOAuthCredentialsStore.CacheEntry.self) {\n                    case let .found(entry):\n                        let parsed = try ClaudeOAuthCredentials.parse(data: entry.data)\n                        #expect(parsed.accessToken == \"keychain-token\")\n                    default:\n                        #expect(Bool(false))\n                    }\n                }\n            }\n        }\n    }\n\n    @Test\n    func `does not sync in background when cache valid and prompt mode only on user action`() throws {\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n        try KeychainCacheStore.withServiceOverrideForTesting(service) {\n            try KeychainAccessGate.withTaskOverrideForTesting(false) {\n                KeychainCacheStore.setTestStoreForTesting(true)\n                defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n                ClaudeOAuthKeychainAccessGate.resetForTesting()\n                defer { ClaudeOAuthKeychainAccessGate.resetForTesting() }\n\n                ClaudeOAuthCredentialsStore.invalidateCache()\n                ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                defer {\n                    ClaudeOAuthCredentialsStore.invalidateCache()\n                    ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                    ClaudeOAuthCredentialsStore._resetClaudeKeychainChangeTrackingForTesting()\n                    ClaudeOAuthCredentialsStore.setClaudeKeychainDataOverrideForTesting(nil)\n                    ClaudeOAuthCredentialsStore.setClaudeKeychainFingerprintOverrideForTesting(nil)\n                }\n\n                let tempDir = FileManager.default.temporaryDirectory\n                    .appendingPathComponent(UUID().uuidString, isDirectory: true)\n                try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n                let fileURL = tempDir.appendingPathComponent(\"credentials.json\")\n\n                try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {\n                    ClaudeOAuthCredentialsStore._resetClaudeKeychainChangeTrackingForTesting()\n\n                    let fingerprintStore = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprintStore()\n                    let fingerprint1 = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                        modifiedAt: 1,\n                        createdAt: 1,\n                        persistentRefHash: \"ref1\")\n                    fingerprintStore.fingerprint = fingerprint1\n\n                    let cacheKey = KeychainCacheStore.Key.oauth(provider: .claude)\n                    let cachedData = self.makeCredentialsData(\n                        accessToken: \"cached-token\",\n                        expiresAt: Date(timeIntervalSinceNow: 3600))\n                    KeychainCacheStore.store(\n                        key: cacheKey,\n                        entry: ClaudeOAuthCredentialsStore.CacheEntry(\n                            data: cachedData,\n                            storedAt: Date(),\n                            owner: .claudeCLI))\n\n                    ClaudeOAuthCredentialsStore._resetClaudeKeychainChangeThrottleForTesting()\n\n                    let fingerprint2 = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                        modifiedAt: 2,\n                        createdAt: 2,\n                        persistentRefHash: \"ref2\")\n                    let keychainData = self.makeCredentialsData(\n                        accessToken: \"keychain-token\",\n                        expiresAt: Date(timeIntervalSinceNow: 3600))\n\n                    let creds = try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.onlyOnUserAction) {\n                        try ProviderInteractionContext.$current.withValue(.background) {\n                            try ClaudeOAuthCredentialsStore.withClaudeKeychainFingerprintStoreOverrideForTesting(\n                                fingerprintStore)\n                            {\n                                try ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(\n                                    data: keychainData,\n                                    fingerprint: fingerprint2)\n                                {\n                                    try ClaudeOAuthCredentialsStore.load(environment: [:], allowKeychainPrompt: false)\n                                }\n                            }\n                        }\n                    }\n\n                    #expect(creds.accessToken == \"cached-token\")\n                    #expect(fingerprintStore.fingerprint == fingerprint1)\n\n                    switch KeychainCacheStore.load(key: cacheKey, as: ClaudeOAuthCredentialsStore.CacheEntry.self) {\n                    case let .found(entry):\n                        let parsed = try ClaudeOAuthCredentials.parse(data: entry.data)\n                        #expect(parsed.accessToken == \"cached-token\")\n                    default:\n                        #expect(Bool(false))\n                    }\n                }\n            }\n        }\n    }\n\n    @Test\n    func `does not sync when claude keychain fingerprint unchanged`() throws {\n        KeychainCacheStore.setTestStoreForTesting(true)\n        defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n        try ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting {\n            try ClaudeOAuthCredentialsStore.withIsolatedCredentialsFileTrackingForTesting {\n                ClaudeOAuthCredentialsStore.invalidateCache()\n                ClaudeOAuthCredentialsStore._resetClaudeKeychainChangeTrackingForTesting()\n                defer {\n                    ClaudeOAuthCredentialsStore.invalidateCache()\n                    ClaudeOAuthCredentialsStore._resetClaudeKeychainChangeTrackingForTesting()\n                }\n\n                let cacheKey = KeychainCacheStore.Key.oauth(provider: .claude)\n                let cachedData = self.makeCredentialsData(\n                    accessToken: \"cached-token\",\n                    expiresAt: Date(timeIntervalSinceNow: 3600))\n                KeychainCacheStore.store(\n                    key: cacheKey,\n                    entry: ClaudeOAuthCredentialsStore.CacheEntry(data: cachedData, storedAt: Date()))\n\n                let fingerprint = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                    modifiedAt: 1,\n                    createdAt: 1,\n                    persistentRefHash: \"ref1\")\n                let first = try ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(\n                    data: cachedData,\n                    fingerprint: fingerprint,\n                    operation: {\n                        try ClaudeOAuthCredentialsStore.load(environment: [:], allowKeychainPrompt: false)\n                    })\n                #expect(first.accessToken == \"cached-token\")\n\n                ClaudeOAuthCredentialsStore._resetClaudeKeychainChangeThrottleForTesting()\n                let keychainData = self.makeCredentialsData(\n                    accessToken: \"keychain-token\",\n                    expiresAt: Date(timeIntervalSinceNow: 3600))\n                let second = try ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(\n                    data: keychainData,\n                    fingerprint: fingerprint,\n                    operation: {\n                        try ClaudeOAuthCredentialsStore.load(environment: [:], allowKeychainPrompt: false)\n                    })\n                #expect(second.accessToken == \"cached-token\")\n\n                switch KeychainCacheStore.load(key: cacheKey, as: ClaudeOAuthCredentialsStore.CacheEntry.self) {\n                case let .found(entry):\n                    let parsed = try ClaudeOAuthCredentials.parse(data: entry.data)\n                    #expect(parsed.accessToken == \"cached-token\")\n                default:\n                    #expect(Bool(false))\n                }\n            }\n        }\n    }\n\n    @Test\n    func `does not sync when keychain credentials expired but cache valid`() throws {\n        KeychainCacheStore.setTestStoreForTesting(true)\n        defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n        try ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting {\n            try ClaudeOAuthCredentialsStore.withIsolatedCredentialsFileTrackingForTesting {\n                ClaudeOAuthCredentialsStore.invalidateCache()\n                ClaudeOAuthCredentialsStore._resetClaudeKeychainChangeTrackingForTesting()\n                defer {\n                    ClaudeOAuthCredentialsStore.invalidateCache()\n                    ClaudeOAuthCredentialsStore._resetClaudeKeychainChangeTrackingForTesting()\n                }\n\n                let cacheKey = KeychainCacheStore.Key.oauth(provider: .claude)\n                let cachedData = self.makeCredentialsData(\n                    accessToken: \"cached-token\",\n                    expiresAt: Date(timeIntervalSinceNow: 3600))\n                KeychainCacheStore.store(\n                    key: cacheKey,\n                    entry: ClaudeOAuthCredentialsStore.CacheEntry(data: cachedData, storedAt: Date()))\n\n                let first = try ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(\n                    data: cachedData,\n                    fingerprint: ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                        modifiedAt: 1,\n                        createdAt: 1,\n                        persistentRefHash: \"ref1\"),\n                    operation: {\n                        try ClaudeOAuthCredentialsStore.load(environment: [:], allowKeychainPrompt: false)\n                    })\n                #expect(first.accessToken == \"cached-token\")\n\n                ClaudeOAuthCredentialsStore._resetClaudeKeychainChangeThrottleForTesting()\n\n                let expiredKeychainData = self.makeCredentialsData(\n                    accessToken: \"expired-keychain-token\",\n                    expiresAt: Date(timeIntervalSinceNow: -3600))\n                let second = try ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(\n                    data: expiredKeychainData,\n                    fingerprint: ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                        modifiedAt: 2,\n                        createdAt: 2,\n                        persistentRefHash: \"ref2\"),\n                    operation: {\n                        try ClaudeOAuthCredentialsStore.load(environment: [:], allowKeychainPrompt: false)\n                    })\n                #expect(second.accessToken == \"cached-token\")\n\n                switch KeychainCacheStore.load(key: cacheKey, as: ClaudeOAuthCredentialsStore.CacheEntry.self) {\n                case let .found(entry):\n                    let parsed = try ClaudeOAuthCredentials.parse(data: entry.data)\n                    #expect(parsed.accessToken == \"cached-token\")\n                default:\n                    #expect(Bool(false))\n                }\n            }\n        }\n    }\n\n    @Test\n    func `respects prompt cooldown gate when disabled prompting`() throws {\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n        try KeychainCacheStore.withServiceOverrideForTesting(service) {\n            KeychainCacheStore.setTestStoreForTesting(true)\n            defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n            ClaudeOAuthKeychainAccessGate.resetForTesting()\n            defer { ClaudeOAuthKeychainAccessGate.resetForTesting() }\n\n            let tempDir = FileManager.default.temporaryDirectory\n                .appendingPathComponent(UUID().uuidString, isDirectory: true)\n            try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n            let fileURL = tempDir.appendingPathComponent(\"credentials.json\")\n            try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {\n                try ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting {\n                    try ClaudeOAuthCredentialsStore.withIsolatedCredentialsFileTrackingForTesting {\n                        ClaudeOAuthCredentialsStore.invalidateCache()\n                        ClaudeOAuthCredentialsStore._resetClaudeKeychainChangeTrackingForTesting()\n                        defer {\n                            ClaudeOAuthCredentialsStore.invalidateCache()\n                            ClaudeOAuthCredentialsStore._resetClaudeKeychainChangeTrackingForTesting()\n                        }\n\n                        let cacheKey = KeychainCacheStore.Key.oauth(provider: .claude)\n                        let cachedData = self.makeCredentialsData(\n                            accessToken: \"cached-token\",\n                            expiresAt: Date(timeIntervalSinceNow: 3600))\n                        KeychainCacheStore.store(\n                            key: cacheKey,\n                            entry: ClaudeOAuthCredentialsStore.CacheEntry(data: cachedData, storedAt: Date()))\n\n                        let first = try ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(\n                            data: cachedData,\n                            fingerprint: ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                                modifiedAt: 1,\n                                createdAt: 1,\n                                persistentRefHash: \"ref1\"),\n                            operation: {\n                                try ClaudeOAuthCredentialsStore.load(environment: [:], allowKeychainPrompt: false)\n                            })\n                        #expect(first.accessToken == \"cached-token\")\n\n                        ClaudeOAuthCredentialsStore._resetClaudeKeychainChangeThrottleForTesting()\n                        ClaudeOAuthKeychainAccessGate.recordDenied(now: Date())\n\n                        let keychainData = self.makeCredentialsData(\n                            accessToken: \"keychain-token\",\n                            expiresAt: Date(timeIntervalSinceNow: 3600))\n                        let second = try ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(\n                            data: keychainData,\n                            fingerprint: ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                                modifiedAt: 2,\n                                createdAt: 2,\n                                persistentRefHash: \"ref2\"),\n                            operation: {\n                                try ClaudeOAuthCredentialsStore.load(\n                                    environment: [:],\n                                    allowKeychainPrompt: false,\n                                    respectKeychainPromptCooldown: true)\n                            })\n                        #expect(second.accessToken == \"cached-token\")\n\n                        switch KeychainCacheStore.load(key: cacheKey, as: ClaudeOAuthCredentialsStore.CacheEntry.self) {\n                        case let .found(entry):\n                            let parsed = try ClaudeOAuthCredentials.parse(data: entry.data)\n                            #expect(parsed.accessToken == \"cached-token\")\n                        default:\n                            #expect(Bool(false))\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    @Test\n    func `sync from claude keychain without prompt respects backoff in background`() {\n        ProviderInteractionContext.$current.withValue(.background) {\n            KeychainAccessGate.withTaskOverrideForTesting(true) {\n                ClaudeOAuthCredentialsStore.withKeychainAccessOverrideForTesting(true) {\n                    let store = ClaudeOAuthCredentialsStore.ClaudeKeychainOverrideStore(\n                        data: self.makeCredentialsData(\n                            accessToken: \"override-token\",\n                            expiresAt: Date(timeIntervalSinceNow: 3600)),\n                        fingerprint: ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                            modifiedAt: 1,\n                            createdAt: 1,\n                            persistentRefHash: \"deadbeefdead\"))\n\n                    let deniedStore = ClaudeOAuthKeychainAccessGate.DeniedUntilStore()\n                    deniedStore.deniedUntil = Date(timeIntervalSinceNow: 3600)\n\n                    ClaudeOAuthKeychainAccessGate.withDeniedUntilStoreOverrideForTesting(deniedStore) {\n                        ClaudeOAuthCredentialsStore.withMutableClaudeKeychainOverrideStoreForTesting(store) {\n                            #expect(ClaudeOAuthCredentialsStore\n                                .syncFromClaudeKeychainWithoutPrompt(now: Date()) == false)\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    @Test\n    func `testing override snapshot forwards mutable Claude keychain override store across detached task`() async {\n        let fingerprint = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n            modifiedAt: 11,\n            createdAt: 7,\n            persistentRefHash: \"snapshot-store\")\n        let store = ClaudeOAuthCredentialsStore.ClaudeKeychainOverrideStore(\n            data: nil,\n            fingerprint: fingerprint)\n\n        let forwarded = await ClaudeOAuthCredentialsStore.withMutableClaudeKeychainOverrideStoreForTesting(store) {\n            let snapshot = ClaudeOAuthCredentialsStore.currentTestingOverridesSnapshotForTask\n\n            return await Task.detached {\n                ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.always) {\n                    ClaudeOAuthCredentialsStore.withTestingOverridesSnapshotForTask(snapshot) {\n                        ClaudeOAuthCredentialsStore.currentClaudeKeychainFingerprintWithoutPromptForAuthGate()\n                    }\n                }\n            }.value\n        }\n\n        #expect(forwarded == fingerprint)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/ClaudeOAuthDelegatedRefreshCoordinatorTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\n@Suite(.serialized)\nstruct ClaudeOAuthDelegatedRefreshCoordinatorTests {\n    private enum StubError: Error, LocalizedError {\n        case failed\n\n        var errorDescription: String? {\n            switch self {\n            case .failed:\n                \"failed\"\n            }\n        }\n    }\n\n    private func makeCredentialsData(accessToken: String, expiresAt: Date) -> Data {\n        let millis = Int(expiresAt.timeIntervalSince1970 * 1000)\n        let json = \"\"\"\n        {\n          \"claudeAiOauth\": {\n            \"accessToken\": \"\\(accessToken)\",\n            \"expiresAt\": \\(millis),\n            \"scopes\": [\"user:profile\"]\n          }\n        }\n        \"\"\"\n        return Data(json.utf8)\n    }\n\n    private func withCoordinatorOverrides<T>(\n        isolateState: Bool = true,\n        cliAvailable: Bool? = nil,\n        touchAuthPath: (@Sendable (TimeInterval, [String: String]) async throws -> Void)? = nil,\n        keychainFingerprint: (@Sendable () -> ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint?)? = nil,\n        operation: () async throws -> T) async rethrows -> T\n    {\n        if isolateState {\n            return try await ClaudeOAuthDelegatedRefreshCoordinator.withIsolatedStateForTesting {\n                ClaudeOAuthDelegatedRefreshCoordinator.resetForTesting()\n                defer { ClaudeOAuthDelegatedRefreshCoordinator.resetForTesting() }\n                return try await ClaudeOAuthDelegatedRefreshCoordinator.withKeychainFingerprintOverrideForTesting(\n                    keychainFingerprint)\n                {\n                    try await ClaudeOAuthDelegatedRefreshCoordinator.withCLIAvailableOverrideForTesting(\n                        cliAvailable)\n                    {\n                        try await ClaudeOAuthDelegatedRefreshCoordinator.withTouchAuthPathOverrideForTesting(\n                            touchAuthPath)\n                        {\n                            try await operation()\n                        }\n                    }\n                }\n            }\n        }\n        ClaudeOAuthDelegatedRefreshCoordinator.resetForTesting()\n        defer { ClaudeOAuthDelegatedRefreshCoordinator.resetForTesting() }\n        return try await ClaudeOAuthDelegatedRefreshCoordinator.withKeychainFingerprintOverrideForTesting(\n            keychainFingerprint)\n        {\n            try await ClaudeOAuthDelegatedRefreshCoordinator.withCLIAvailableOverrideForTesting(cliAvailable) {\n                try await ClaudeOAuthDelegatedRefreshCoordinator.withTouchAuthPathOverrideForTesting(\n                    touchAuthPath)\n                {\n                    try await operation()\n                }\n            }\n        }\n    }\n\n    @Test\n    func `cooldown prevents repeated attempts`() async {\n        ClaudeOAuthDelegatedRefreshCoordinator.resetForTesting()\n        defer { ClaudeOAuthDelegatedRefreshCoordinator.resetForTesting() }\n\n        final class FingerprintBox: @unchecked Sendable {\n            var fingerprint: ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint?\n            init(_ fingerprint: ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint?) {\n                self.fingerprint = fingerprint\n            }\n        }\n        let box = FingerprintBox(ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n            modifiedAt: 1,\n            createdAt: 1,\n            persistentRefHash: \"ref1\"))\n        let start = Date(timeIntervalSince1970: 10000)\n        let (first, second) = await self.withCoordinatorOverrides(\n            cliAvailable: true,\n            touchAuthPath: { _, _ in\n                box.fingerprint = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                    modifiedAt: 2,\n                    createdAt: 2,\n                    persistentRefHash: \"ref2\")\n            },\n            keychainFingerprint: { box.fingerprint },\n            operation: {\n                let first = await ClaudeOAuthDelegatedRefreshCoordinator.attempt(now: start, timeout: 0.1)\n                let second = await ClaudeOAuthDelegatedRefreshCoordinator\n                    .attempt(now: start.addingTimeInterval(30), timeout: 0.1)\n                return (first, second)\n            })\n\n        #expect(first == .attemptedSucceeded)\n        #expect(second == .skippedByCooldown)\n    }\n\n    @Test\n    func `cli unavailable returns cli unavailable`() async {\n        ClaudeOAuthDelegatedRefreshCoordinator.resetForTesting()\n        defer { ClaudeOAuthDelegatedRefreshCoordinator.resetForTesting() }\n\n        let outcome = await self.withCoordinatorOverrides(cliAvailable: false, operation: {\n            await ClaudeOAuthDelegatedRefreshCoordinator.attempt(\n                now: Date(timeIntervalSince1970: 20000),\n                timeout: 0.1)\n        })\n\n        #expect(outcome == .cliUnavailable)\n    }\n\n    @Test\n    func `successful auth touch reports attempted succeeded`() async {\n        ClaudeOAuthDelegatedRefreshCoordinator.resetForTesting()\n        defer { ClaudeOAuthDelegatedRefreshCoordinator.resetForTesting() }\n\n        final class FingerprintBox: @unchecked Sendable {\n            var fingerprint: ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint?\n            init(_ fingerprint: ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint?) {\n                self.fingerprint = fingerprint\n            }\n        }\n        let box = FingerprintBox(ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n            modifiedAt: 10,\n            createdAt: 10,\n            persistentRefHash: \"refA\"))\n        let outcome = await self.withCoordinatorOverrides(\n            cliAvailable: true,\n            touchAuthPath: { _, _ in\n                box.fingerprint = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                    modifiedAt: 11,\n                    createdAt: 11,\n                    persistentRefHash: \"refB\")\n            },\n            keychainFingerprint: { box.fingerprint },\n            operation: {\n                await ClaudeOAuthDelegatedRefreshCoordinator.attempt(\n                    now: Date(timeIntervalSince1970: 30000),\n                    timeout: 0.1)\n            })\n\n        #expect(outcome == .attemptedSucceeded)\n    }\n\n    @Test\n    func `failed auth touch reports attempted failed`() async throws {\n        ClaudeOAuthDelegatedRefreshCoordinator.resetForTesting()\n        defer { ClaudeOAuthDelegatedRefreshCoordinator.resetForTesting() }\n\n        let outcome = try await self.withCoordinatorOverrides(\n            cliAvailable: true,\n            touchAuthPath: { _, _ in\n                throw StubError.failed\n            },\n            keychainFingerprint: {\n                ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                    modifiedAt: 20,\n                    createdAt: 20,\n                    persistentRefHash: \"refX\")\n            },\n            operation: {\n                await KeychainAccessGate.withTaskOverrideForTesting(false) {\n                    await ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(.securityFramework) {\n                        await ClaudeOAuthDelegatedRefreshCoordinator.attempt(\n                            now: Date(timeIntervalSince1970: 40000),\n                            timeout: 0.1)\n                    }\n                }\n            })\n\n        guard case let .attemptedFailed(message) = outcome else {\n            Issue.record(\"Expected .attemptedFailed outcome\")\n            return\n        }\n        #expect(message.contains(\"failed\"))\n    }\n\n    @Test\n    func `environment CLI override avoids CLI unavailable`() async throws {\n        ClaudeOAuthDelegatedRefreshCoordinator.resetForTesting()\n        defer { ClaudeOAuthDelegatedRefreshCoordinator.resetForTesting() }\n\n        let stubCLI = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)\n        let script = \"#!/bin/sh\\nexit 0\\n\"\n        try script.write(to: stubCLI, atomically: true, encoding: .utf8)\n        try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: stubCLI.path)\n\n        let outcome = try await self.withCoordinatorOverrides(\n            touchAuthPath: { _, environment in\n                #expect(environment[\"CLAUDE_CLI_PATH\"] == stubCLI.path)\n                throw StubError.failed\n            },\n            keychainFingerprint: {\n                ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                    modifiedAt: 20,\n                    createdAt: 20,\n                    persistentRefHash: \"ref-env\")\n            },\n            operation: {\n                await KeychainAccessGate.withTaskOverrideForTesting(false) {\n                    await ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(.securityFramework) {\n                        await ClaudeOAuthDelegatedRefreshCoordinator.attempt(\n                            now: Date(timeIntervalSince1970: 45000),\n                            timeout: 0.1,\n                            environment: [\"CLAUDE_CLI_PATH\": stubCLI.path])\n                    }\n                }\n            })\n\n        guard case let .attemptedFailed(message) = outcome else {\n            Issue.record(\"Expected env-provided CLI override to reach touch attempt\")\n            return\n        }\n        #expect(message.contains(\"failed\"))\n    }\n\n    @Test\n    func `concurrent attempts join in flight`() async {\n        ClaudeOAuthDelegatedRefreshCoordinator.resetForTesting()\n        defer { ClaudeOAuthDelegatedRefreshCoordinator.resetForTesting() }\n\n        actor Gate {\n            private var startedContinuations: [CheckedContinuation<Void, Never>] = []\n            private var releaseContinuations: [CheckedContinuation<Void, Never>] = []\n            private var hasStarted = false\n            private var isReleased = false\n\n            func markStarted() {\n                self.hasStarted = true\n                let continuations = self.startedContinuations\n                self.startedContinuations.removeAll()\n                continuations.forEach { $0.resume() }\n            }\n\n            func waitStarted() async {\n                if self.hasStarted { return }\n                await withCheckedContinuation { cont in\n                    self.startedContinuations.append(cont)\n                }\n            }\n\n            func release() {\n                self.isReleased = true\n                let continuations = self.releaseContinuations\n                self.releaseContinuations.removeAll()\n                continuations.forEach { $0.resume() }\n            }\n\n            func waitRelease() async {\n                if self.isReleased { return }\n                await withCheckedContinuation { cont in\n                    self.releaseContinuations.append(cont)\n                }\n            }\n        }\n\n        final class FingerprintBox: @unchecked Sendable {\n            var fingerprint: ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint?\n            init(_ fingerprint: ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint?) {\n                self.fingerprint = fingerprint\n            }\n        }\n\n        final class CounterBox: @unchecked Sendable {\n            private let lock = NSLock()\n            private(set) var count: Int = 0\n            func increment() {\n                self.lock.lock()\n                self.count += 1\n                self.lock.unlock()\n            }\n        }\n\n        let counter = CounterBox()\n        let gate = Gate()\n        let box = FingerprintBox(ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n            modifiedAt: 1,\n            createdAt: 1,\n            persistentRefHash: \"ref1\"))\n        let now = Date(timeIntervalSince1970: 50000)\n        let outcomes = await self.withCoordinatorOverrides(\n            isolateState: false,\n            cliAvailable: true,\n            touchAuthPath: { _, _ in\n                counter.increment()\n                await gate.markStarted()\n                await gate.waitRelease()\n                box.fingerprint = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                    modifiedAt: 2,\n                    createdAt: 2,\n                    persistentRefHash: \"ref2\")\n            },\n            keychainFingerprint: { box.fingerprint },\n            operation: {\n                let first = Task {\n                    await ClaudeOAuthDelegatedRefreshCoordinator.attempt(now: now, timeout: 2)\n                }\n                await gate.waitStarted()\n                let second = Task {\n                    await ClaudeOAuthDelegatedRefreshCoordinator.attempt(\n                        now: now.addingTimeInterval(30),\n                        timeout: 2)\n                }\n\n                await gate.release()\n                return await [first.value, second.value]\n            })\n\n        #expect(outcomes.allSatisfy { $0 == .attemptedSucceeded })\n        #expect(counter.count == 1)\n    }\n\n    @Test\n    func `experimental strategy does not use security framework fingerprint observation`() async {\n        ClaudeOAuthDelegatedRefreshCoordinator.resetForTesting()\n        defer { ClaudeOAuthDelegatedRefreshCoordinator.resetForTesting() }\n        await ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(\n            .securityCLIExperimental)\n        {\n            final class CounterBox: @unchecked Sendable {\n                private let lock = NSLock()\n                private(set) var count: Int = 0\n                func increment() {\n                    self.lock.lock()\n                    self.count += 1\n                    self.lock.unlock()\n                }\n            }\n            let fingerprintCounter = CounterBox()\n            let securityData = self.makeCredentialsData(\n                accessToken: \"security-token-a\",\n                expiresAt: Date(timeIntervalSinceNow: 3600))\n            let outcome = await self.withCoordinatorOverrides(\n                cliAvailable: true,\n                touchAuthPath: { _, _ in },\n                keychainFingerprint: {\n                    fingerprintCounter.increment()\n                    return ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                        modifiedAt: 1,\n                        createdAt: 1,\n                        persistentRefHash: \"framework-fingerprint\")\n                },\n                operation: {\n                    await ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting(.data(securityData)) {\n                        await ClaudeOAuthDelegatedRefreshCoordinator.attempt(\n                            now: Date(timeIntervalSince1970: 60000),\n                            timeout: 0.1)\n                    }\n                })\n\n            guard case .attemptedFailed = outcome else {\n                Issue.record(\"Expected .attemptedFailed outcome\")\n                return\n            }\n            #expect(fingerprintCounter.count < 1)\n        }\n    }\n\n    @Test\n    func `experimental strategy observes security CLI change after touch`() async {\n        ClaudeOAuthDelegatedRefreshCoordinator.resetForTesting()\n        defer { ClaudeOAuthDelegatedRefreshCoordinator.resetForTesting() }\n        await ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(\n            .securityCLIExperimental)\n        {\n            final class DataBox: @unchecked Sendable {\n                private let lock = NSLock()\n                private var _data: Data?\n                init(data: Data?) {\n                    self._data = data\n                }\n\n                func load() -> Data? {\n                    self.lock.lock()\n                    defer { self.lock.unlock() }\n                    return self._data\n                }\n\n                func store(_ data: Data?) {\n                    self.lock.lock()\n                    self._data = data\n                    self.lock.unlock()\n                }\n            }\n            final class CounterBox: @unchecked Sendable {\n                private let lock = NSLock()\n                private(set) var count: Int = 0\n                func increment() {\n                    self.lock.lock()\n                    self.count += 1\n                    self.lock.unlock()\n                }\n            }\n            let beforeData = self.makeCredentialsData(\n                accessToken: \"security-token-before\",\n                expiresAt: Date(timeIntervalSinceNow: -60))\n            let afterData = self.makeCredentialsData(\n                accessToken: \"security-token-after\",\n                expiresAt: Date(timeIntervalSinceNow: 3600))\n            let dataBox = DataBox(data: beforeData)\n            let fingerprintCounter = CounterBox()\n            let outcome = await self.withCoordinatorOverrides(\n                cliAvailable: true,\n                touchAuthPath: { _, _ in\n                    dataBox.store(afterData)\n                },\n                keychainFingerprint: {\n                    fingerprintCounter.increment()\n                    return ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                        modifiedAt: 11,\n                        createdAt: 11,\n                        persistentRefHash: \"framework-fingerprint\")\n                },\n                operation: {\n                    await ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting(\n                        .dynamic { _ in dataBox.load() })\n                    {\n                        await ClaudeOAuthDelegatedRefreshCoordinator.attempt(\n                            now: Date(timeIntervalSince1970: 61000),\n                            timeout: 0.1)\n                    }\n                })\n\n            #expect(outcome == .attemptedSucceeded)\n            #expect(fingerprintCounter.count < 1)\n        }\n    }\n\n    @Test\n    func `experimental strategy missing baseline does not auto succeed when later read succeeds`() async {\n        ClaudeOAuthDelegatedRefreshCoordinator.resetForTesting()\n        defer { ClaudeOAuthDelegatedRefreshCoordinator.resetForTesting() }\n        await ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(\n            .securityCLIExperimental)\n        {\n            final class DataBox: @unchecked Sendable {\n                private let lock = NSLock()\n                private var _data: Data?\n                init(data: Data?) {\n                    self._data = data\n                }\n\n                func load() -> Data? {\n                    self.lock.lock()\n                    defer { self.lock.unlock() }\n                    return self._data\n                }\n\n                func store(_ data: Data?) {\n                    self.lock.lock()\n                    self._data = data\n                    self.lock.unlock()\n                }\n            }\n            final class CounterBox: @unchecked Sendable {\n                private let lock = NSLock()\n                private(set) var count: Int = 0\n                func increment() {\n                    self.lock.lock()\n                    self.count += 1\n                    self.lock.unlock()\n                }\n            }\n            let afterData = self.makeCredentialsData(\n                accessToken: \"security-token-after-baseline-miss\",\n                expiresAt: Date(timeIntervalSinceNow: 3600))\n            let dataBox = DataBox(data: nil)\n            let fingerprintCounter = CounterBox()\n            let outcome = await self.withCoordinatorOverrides(\n                cliAvailable: true,\n                touchAuthPath: { _, _ in\n                    dataBox.store(afterData)\n                },\n                keychainFingerprint: {\n                    fingerprintCounter.increment()\n                    return ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                        modifiedAt: 21,\n                        createdAt: 21,\n                        persistentRefHash: \"framework-fingerprint\")\n                },\n                operation: {\n                    await ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting(\n                        .dynamic { _ in dataBox.load() })\n                    {\n                        await ClaudeOAuthDelegatedRefreshCoordinator.attempt(\n                            now: Date(timeIntervalSince1970: 61500),\n                            timeout: 0.1)\n                    }\n                })\n\n            guard case .attemptedFailed = outcome else {\n                Issue.record(\"Expected .attemptedFailed outcome when baseline is unavailable\")\n                return\n            }\n            #expect(fingerprintCounter.count < 1)\n        }\n    }\n\n    @Test\n    func `experimental strategy observation skips security CLI when global keychain disabled`() async {\n        ClaudeOAuthDelegatedRefreshCoordinator.resetForTesting()\n        defer { ClaudeOAuthDelegatedRefreshCoordinator.resetForTesting() }\n        await ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(\n            .securityCLIExperimental)\n        {\n            final class CounterBox: @unchecked Sendable {\n                private let lock = NSLock()\n                private(set) var count: Int = 0\n                func increment() {\n                    self.lock.lock()\n                    self.count += 1\n                    self.lock.unlock()\n                }\n            }\n\n            let securityReadCounter = CounterBox()\n            let securityData = self.makeCredentialsData(\n                accessToken: \"security-should-not-be-read\",\n                expiresAt: Date(timeIntervalSinceNow: 3600))\n            let outcome = await self.withCoordinatorOverrides(\n                cliAvailable: true,\n                touchAuthPath: { _, _ in },\n                operation: {\n                    await ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting(.dynamic { _ in\n                        securityReadCounter.increment()\n                        return securityData\n                    }) {\n                        await KeychainAccessGate.withTaskOverrideForTesting(true) {\n                            await ClaudeOAuthDelegatedRefreshCoordinator.attempt(\n                                now: Date(timeIntervalSince1970: 62000),\n                                timeout: 0.1)\n                        }\n                    }\n                })\n\n            guard case .attemptedFailed = outcome else {\n                Issue.record(\"Expected .attemptedFailed outcome\")\n                return\n            }\n            #expect(securityReadCounter.count < 1)\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/ClaudeOAuthDelegatedRefreshRecoveryTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\n@Suite(.serialized)\nstruct ClaudeOAuthDelegatedRefreshRecoveryTests {\n    private actor AsyncCounter {\n        private var value = 0\n\n        func increment() -> Int {\n            self.value += 1\n            return self.value\n        }\n\n        func current() -> Int {\n            self.value\n        }\n    }\n\n    private actor TokenCapture {\n        private var token: String?\n\n        func set(_ token: String) {\n            self.token = token\n        }\n\n        func get() -> String? {\n            self.token\n        }\n    }\n\n    private static func makeOAuthUsageResponse() throws -> OAuthUsageResponse {\n        let json = \"\"\"\n        {\n          \"five_hour\": { \"utilization\": 7, \"resets_at\": \"2025-12-23T16:00:00.000Z\" },\n          \"seven_day\": { \"utilization\": 21, \"resets_at\": \"2025-12-29T23:00:00.000Z\" }\n        }\n        \"\"\"\n        return try ClaudeOAuthUsageFetcher._decodeUsageResponseForTesting(Data(json.utf8))\n    }\n\n    private func makeCredentialsData(accessToken: String, expiresAt: Date, refreshToken: String? = nil) -> Data {\n        let millis = Int(expiresAt.timeIntervalSince1970 * 1000)\n        let refreshField: String = {\n            guard let refreshToken else { return \"\" }\n            return \",\\n            \\\"refreshToken\\\": \\\"\\(refreshToken)\\\"\"\n        }()\n        let json = \"\"\"\n        {\n          \"claudeAiOauth\": {\n            \"accessToken\": \"\\(accessToken)\",\n            \"expiresAt\": \\(millis),\n            \"scopes\": [\"user:profile\"]\\(refreshField)\n          }\n        }\n        \"\"\"\n        return Data(json.utf8)\n    }\n\n    @Test\n    func `silent keychain repair recovers without delegation`() async throws {\n        let delegatedCounter = AsyncCounter()\n        let usageResponse = try Self.makeOAuthUsageResponse()\n        let tokenCapture = TokenCapture()\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n\n        try await KeychainCacheStore.withServiceOverrideForTesting(service) {\n            try await KeychainAccessGate.withTaskOverrideForTesting(false) {\n                KeychainCacheStore.setTestStoreForTesting(true)\n                defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n                ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                defer { ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting() }\n                ClaudeOAuthCredentialsStore._resetClaudeKeychainChangeTrackingForTesting()\n                defer { ClaudeOAuthCredentialsStore._resetClaudeKeychainChangeTrackingForTesting() }\n\n                try await ClaudeOAuthCredentialsStore.withIsolatedCredentialsFileTrackingForTesting {\n                    try await ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting {\n                        let tempDir = FileManager.default.temporaryDirectory\n                            .appendingPathComponent(UUID().uuidString, isDirectory: true)\n                        try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n                        let fileURL = tempDir.appendingPathComponent(\"credentials.json\")\n                        let snapshot = try await ClaudeOAuthCredentialsStore\n                            .withCredentialsURLOverrideForTesting(fileURL) {\n                                // Seed an expired cache entry owned by Claude CLI, so the initial load delegates\n                                // refresh.\n                                ClaudeOAuthCredentialsStore.invalidateCache()\n                                let expiredData = self.makeCredentialsData(\n                                    accessToken: \"expired-token\",\n                                    expiresAt: Date(timeIntervalSinceNow: -3600))\n                                let cacheKey = KeychainCacheStore.Key.oauth(provider: .claude)\n                                let cacheEntry = ClaudeOAuthCredentialsStore.CacheEntry(\n                                    data: expiredData,\n                                    storedAt: Date(),\n                                    owner: .claudeCLI)\n                                KeychainCacheStore.store(key: cacheKey, entry: cacheEntry)\n                                defer { KeychainCacheStore.clear(key: cacheKey) }\n\n                                // Sanity: setup should be visible to the code under test.\n                                // Otherwise it may attempt interactive reads.\n                                #expect(ClaudeOAuthCredentialsStore.hasCachedCredentials(environment: [:]) == true)\n\n                                // Simulate Claude CLI writing fresh credentials into the Claude Code keychain entry.\n                                let freshData = self.makeCredentialsData(\n                                    accessToken: \"fresh-token\",\n                                    expiresAt: Date(timeIntervalSinceNow: 3600))\n                                let fingerprint = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                                    modifiedAt: 1,\n                                    createdAt: 1,\n                                    persistentRefHash: \"test\")\n\n                                let fetcher = ClaudeUsageFetcher(\n                                    browserDetection: BrowserDetection(cacheTTL: 0),\n                                    environment: [:],\n                                    dataSource: .oauth,\n                                    oauthKeychainPromptCooldownEnabled: true)\n\n                                let fetchOverride: (@Sendable (String) async throws -> OAuthUsageResponse)? = { token in\n                                    await tokenCapture.set(token)\n                                    return usageResponse\n                                }\n                                let delegatedOverride: (@Sendable (\n                                    Date,\n                                    TimeInterval,\n                                    [String: String]) async -> ClaudeOAuthDelegatedRefreshCoordinator.Outcome)? =\n                                    { _, _, _ in\n                                        _ = await delegatedCounter.increment()\n                                        return .attemptedSucceeded\n                                    }\n\n                                let snapshot = try await ClaudeOAuthKeychainPromptPreference\n                                    .withTaskOverrideForTesting(.onlyOnUserAction) {\n                                        try await ProviderInteractionContext.$current.withValue(.userInitiated) {\n                                            try await ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(\n                                                data: freshData,\n                                                fingerprint: fingerprint)\n                                            {\n                                                try await ClaudeUsageFetcher.$fetchOAuthUsageOverride\n                                                    .withValue(fetchOverride) {\n                                                        try await ClaudeUsageFetcher.$delegatedRefreshAttemptOverride\n                                                            .withValue(delegatedOverride) {\n                                                                try await fetcher.loadLatestUsage(model: \"sonnet\")\n                                                            }\n                                                    }\n                                            }\n                                        }\n                                    }\n\n                                // If Claude keychain already contains fresh credentials, we should recover without\n                                // needing a\n                                // CLI\n                                // touch.\n                                #expect(await delegatedCounter.current() == 0)\n                                #expect(await tokenCapture.get() == \"fresh-token\")\n                                #expect(snapshot.primary.usedPercent == 7)\n                                #expect(snapshot.secondary?.usedPercent == 21)\n                                return snapshot\n                            }\n                        _ = snapshot\n                    }\n                }\n            }\n        }\n    }\n\n    @Test\n    func `delegated refresh attempted succeeded recovers after keychain sync`() async throws {\n        let delegatedCounter = AsyncCounter()\n        let usageResponse = try Self.makeOAuthUsageResponse()\n        let tokenCapture = TokenCapture()\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n\n        try await KeychainCacheStore.withServiceOverrideForTesting(service) {\n            try await KeychainAccessGate.withTaskOverrideForTesting(false) {\n                KeychainCacheStore.setTestStoreForTesting(true)\n                defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n                ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                defer { ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting() }\n                ClaudeOAuthCredentialsStore._resetClaudeKeychainChangeTrackingForTesting()\n                defer { ClaudeOAuthCredentialsStore._resetClaudeKeychainChangeTrackingForTesting() }\n\n                try await ClaudeOAuthCredentialsStore.withIsolatedCredentialsFileTrackingForTesting {\n                    try await ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting {\n                        let tempDir = FileManager.default.temporaryDirectory\n                            .appendingPathComponent(UUID().uuidString, isDirectory: true)\n                        try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n                        let fileURL = tempDir.appendingPathComponent(\"credentials.json\")\n                        let snapshot = try await ClaudeOAuthCredentialsStore\n                            .withCredentialsURLOverrideForTesting(fileURL) {\n                                // Seed an expired cache entry owned by Claude CLI, so the initial load delegates\n                                // refresh.\n                                ClaudeOAuthCredentialsStore.invalidateCache()\n                                let expiredData = self.makeCredentialsData(\n                                    accessToken: \"expired-token\",\n                                    expiresAt: Date(timeIntervalSinceNow: -3600))\n                                let cacheKey = KeychainCacheStore.Key.oauth(provider: .claude)\n                                let cacheEntry = ClaudeOAuthCredentialsStore.CacheEntry(\n                                    data: expiredData,\n                                    storedAt: Date(),\n                                    owner: .claudeCLI)\n                                KeychainCacheStore.store(key: cacheKey, entry: cacheEntry)\n                                defer { KeychainCacheStore.clear(key: cacheKey) }\n\n                                // Ensure we don't silently repair from the Claude keychain before delegation.\n                                // Use an explicit empty-data override so we never consult the real system Keychain\n                                // during\n                                // tests.\n                                let stubFingerprint = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                                    modifiedAt: 1,\n                                    createdAt: 1,\n                                    persistentRefHash: \"test\")\n                                let keychainOverrideStore = ClaudeOAuthCredentialsStore.ClaudeKeychainOverrideStore(\n                                    data: Data(),\n                                    fingerprint: stubFingerprint)\n\n                                let freshData = self.makeCredentialsData(\n                                    accessToken: \"fresh-token\",\n                                    expiresAt: Date(timeIntervalSinceNow: 3600))\n\n                                let fetcher = ClaudeUsageFetcher(\n                                    browserDetection: BrowserDetection(cacheTTL: 0),\n                                    environment: [:],\n                                    dataSource: .oauth,\n                                    oauthKeychainPromptCooldownEnabled: true)\n\n                                let fetchOverride: (@Sendable (String) async throws -> OAuthUsageResponse)? = { token in\n                                    await tokenCapture.set(token)\n                                    return usageResponse\n                                }\n\n                                let delegatedOverride: (@Sendable (\n                                    Date,\n                                    TimeInterval,\n                                    [String: String]) async -> ClaudeOAuthDelegatedRefreshCoordinator.Outcome)? =\n                                    { _, _, _ in\n                                        // Simulate Claude CLI writing fresh credentials after the delegated refresh\n                                        // touch.\n                                        keychainOverrideStore.data = freshData\n                                        keychainOverrideStore.fingerprint = stubFingerprint\n                                        _ = await delegatedCounter.increment()\n                                        return .attemptedSucceeded\n                                    }\n\n                                let snapshot = try await ClaudeOAuthKeychainPromptPreference\n                                    .withTaskOverrideForTesting(.always) {\n                                        try await ProviderInteractionContext.$current.withValue(.userInitiated) {\n                                            try await ClaudeOAuthCredentialsStore\n                                                .withMutableClaudeKeychainOverrideStoreForTesting(\n                                                    keychainOverrideStore)\n                                                {\n                                                    try await ClaudeUsageFetcher.$fetchOAuthUsageOverride\n                                                        .withValue(fetchOverride) {\n                                                            try await ClaudeUsageFetcher\n                                                                .$delegatedRefreshAttemptOverride\n                                                                .withValue(delegatedOverride) {\n                                                                    try await fetcher.loadLatestUsage(model: \"sonnet\")\n                                                                }\n                                                        }\n                                                }\n                                        }\n                                    }\n\n                                #expect(await delegatedCounter.current() == 1)\n                                let capturedToken = await tokenCapture.get()\n                                if capturedToken != \"fresh-token\" {\n                                    Issue.record(\"Expected fresh-token, got \\(capturedToken ?? \"nil\")\")\n                                }\n                                #expect(capturedToken == \"fresh-token\")\n                                #expect(snapshot.primary.usedPercent == 7)\n                                #expect(snapshot.secondary?.usedPercent == 21)\n                                return snapshot\n                            }\n                        _ = snapshot\n                    }\n                }\n            }\n        }\n    }\n\n    @Test\n    func `delegated refresh attempted succeeded background only on user action does not recover from keychain`()\n        async throws\n    {\n        let delegatedCounter = AsyncCounter()\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n\n        try await KeychainCacheStore.withServiceOverrideForTesting(service) {\n            try await KeychainAccessGate.withTaskOverrideForTesting(false) {\n                KeychainCacheStore.setTestStoreForTesting(true)\n                defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n                ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                defer { ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting() }\n                ClaudeOAuthCredentialsStore._resetClaudeKeychainChangeTrackingForTesting()\n                defer { ClaudeOAuthCredentialsStore._resetClaudeKeychainChangeTrackingForTesting() }\n\n                try await ClaudeOAuthCredentialsStore.withIsolatedCredentialsFileTrackingForTesting {\n                    try await ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting {\n                        let tempDir = FileManager.default.temporaryDirectory\n                            .appendingPathComponent(UUID().uuidString, isDirectory: true)\n                        try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n                        let fileURL = tempDir.appendingPathComponent(\"credentials.json\")\n\n                        await ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {\n                            ClaudeOAuthCredentialsStore.invalidateCache()\n                            let expiredData = self.makeCredentialsData(\n                                accessToken: \"expired-token\",\n                                expiresAt: Date(timeIntervalSinceNow: -3600))\n                            let cacheKey = KeychainCacheStore.Key.oauth(provider: .claude)\n                            let cacheEntry = ClaudeOAuthCredentialsStore.CacheEntry(\n                                data: expiredData,\n                                storedAt: Date(),\n                                owner: .claudeCLI)\n                            KeychainCacheStore.store(key: cacheKey, entry: cacheEntry)\n                            defer { KeychainCacheStore.clear(key: cacheKey) }\n\n                            // Expired Claude-CLI-owned credentials are still considered cache-present (delegatable).\n                            #expect(ClaudeOAuthCredentialsStore.hasCachedCredentials(environment: [:]) == true)\n\n                            let stubFingerprint = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                                modifiedAt: 1,\n                                createdAt: 1,\n                                persistentRefHash: \"test\")\n                            let keychainOverrideStore = ClaudeOAuthCredentialsStore.ClaudeKeychainOverrideStore(\n                                data: Data(),\n                                fingerprint: stubFingerprint)\n                            let freshData = self.makeCredentialsData(\n                                accessToken: \"fresh-token\",\n                                expiresAt: Date(timeIntervalSinceNow: 3600))\n\n                            let fetcher = ClaudeUsageFetcher(\n                                browserDetection: BrowserDetection(cacheTTL: 0),\n                                environment: [:],\n                                dataSource: .oauth,\n                                oauthKeychainPromptCooldownEnabled: false,\n                                allowBackgroundDelegatedRefresh: true)\n\n                            let delegatedOverride: (@Sendable (\n                                Date,\n                                TimeInterval,\n                                [String: String]) async -> ClaudeOAuthDelegatedRefreshCoordinator.Outcome)? =\n                                { _, _, _ in\n                                    keychainOverrideStore.data = freshData\n                                    keychainOverrideStore.fingerprint = stubFingerprint\n                                    _ = await delegatedCounter.increment()\n                                    return .attemptedSucceeded\n                                }\n\n                            do {\n                                _ = try await ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(\n                                    .onlyOnUserAction)\n                                {\n                                    try await ProviderInteractionContext.$current.withValue(.background) {\n                                        try await ClaudeOAuthCredentialsStore\n                                            .withMutableClaudeKeychainOverrideStoreForTesting(keychainOverrideStore) {\n                                                try await ClaudeUsageFetcher.$delegatedRefreshAttemptOverride\n                                                    .withValue(delegatedOverride) {\n                                                        try await fetcher.loadLatestUsage(model: \"sonnet\")\n                                                    }\n                                            }\n                                    }\n                                }\n                                Issue.record(\n                                    \"Expected OAuth fetch failure: background keychain recovery should stay blocked\")\n                            } catch let error as ClaudeUsageError {\n                                guard case let .oauthFailed(message) = error else {\n                                    Issue.record(\"Expected ClaudeUsageError.oauthFailed, got \\(error)\")\n                                    return\n                                }\n                                #expect(message.contains(\"still unavailable after delegated Claude CLI refresh\"))\n                            } catch {\n                                Issue.record(\"Expected ClaudeUsageError, got \\(error)\")\n                            }\n\n                            #expect(await delegatedCounter.current() == 1)\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/ClaudeOAuthFetchStrategyAvailabilityTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\n#if os(macOS)\n@Suite(.serialized)\nstruct ClaudeOAuthFetchStrategyAvailabilityTests {\n    private struct StubClaudeFetcher: ClaudeUsageFetching {\n        func loadLatestUsage(model _: String) async throws -> ClaudeUsageSnapshot {\n            throw ClaudeUsageError.parseFailed(\"stub\")\n        }\n\n        func debugRawProbe(model _: String) async -> String {\n            \"stub\"\n        }\n\n        func detectVersion() -> String? {\n            nil\n        }\n    }\n\n    private func makeContext(\n        sourceMode: ProviderSourceMode,\n        env: [String: String] = [:]) -> ProviderFetchContext\n    {\n        ProviderFetchContext(\n            runtime: .app,\n            sourceMode: sourceMode,\n            includeCredits: false,\n            webTimeout: 1,\n            webDebugDumpHTML: false,\n            verbose: false,\n            env: env,\n            settings: nil,\n            fetcher: UsageFetcher(environment: env),\n            claudeFetcher: StubClaudeFetcher(),\n            browserDetection: BrowserDetection(cacheTTL: 0))\n    }\n\n    private func expiredRecord(owner: ClaudeOAuthCredentialOwner = .claudeCLI) -> ClaudeOAuthCredentialRecord {\n        ClaudeOAuthCredentialRecord(\n            credentials: ClaudeOAuthCredentials(\n                accessToken: \"expired-token\",\n                refreshToken: \"refresh-token\",\n                expiresAt: Date(timeIntervalSinceNow: -60),\n                scopes: [\"user:profile\"],\n                rateLimitTier: nil),\n            owner: owner,\n            source: .cacheKeychain)\n    }\n\n    @Test\n    func `auto mode expired creds cli available returns available`() async {\n        let context = self.makeContext(sourceMode: .auto)\n        let strategy = ClaudeOAuthFetchStrategy()\n        let available = await ClaudeOAuthFetchStrategy.$nonInteractiveCredentialRecordOverride\n            .withValue(self.expiredRecord()) {\n                await ClaudeOAuthFetchStrategy.$claudeCLIAvailableOverride.withValue(true) {\n                    await strategy.isAvailable(context)\n                }\n            }\n        #expect(available == true)\n    }\n\n    @Test\n    func `auto mode expired creds cli unavailable returns unavailable`() async {\n        let context = self.makeContext(sourceMode: .auto)\n        let strategy = ClaudeOAuthFetchStrategy()\n        let available = await ClaudeOAuthFetchStrategy.$nonInteractiveCredentialRecordOverride\n            .withValue(self.expiredRecord()) {\n                await ClaudeOAuthFetchStrategy.$claudeCLIAvailableOverride.withValue(false) {\n                    await strategy.isAvailable(context)\n                }\n            }\n        #expect(available == false)\n    }\n\n    @Test\n    func `oauth mode expired creds cli available returns available`() async {\n        let context = self.makeContext(sourceMode: .oauth)\n        let strategy = ClaudeOAuthFetchStrategy()\n        let available = await ClaudeOAuthFetchStrategy.$nonInteractiveCredentialRecordOverride\n            .withValue(self.expiredRecord()) {\n                await ClaudeOAuthFetchStrategy.$claudeCLIAvailableOverride.withValue(true) {\n                    await strategy.isAvailable(context)\n                }\n            }\n        #expect(available == true)\n    }\n\n    @Test\n    func `auto mode expired codexbar creds cli unavailable still available`() async {\n        let context = self.makeContext(sourceMode: .auto)\n        let strategy = ClaudeOAuthFetchStrategy()\n        let available = await ClaudeOAuthFetchStrategy.$nonInteractiveCredentialRecordOverride\n            .withValue(self.expiredRecord(owner: .codexbar)) {\n                await ClaudeOAuthFetchStrategy.$claudeCLIAvailableOverride.withValue(false) {\n                    await strategy.isAvailable(context)\n                }\n            }\n        #expect(available == true)\n    }\n\n    @Test\n    func `oauth mode does not fallback after O auth failure`() {\n        let context = self.makeContext(sourceMode: .oauth)\n        let strategy = ClaudeOAuthFetchStrategy()\n        #expect(strategy.shouldFallback(\n            on: ClaudeUsageError.oauthFailed(\"oauth failed\"),\n            context: context) == false)\n    }\n\n    @Test\n    func `auto mode falls back after O auth failure`() {\n        let context = self.makeContext(sourceMode: .auto)\n        let strategy = ClaudeOAuthFetchStrategy()\n        #expect(strategy.shouldFallback(\n            on: ClaudeUsageError.oauthFailed(\"oauth failed\"),\n            context: context) == true)\n    }\n\n    @Test\n    func `auto mode user initiated clears keychain cooldown gate`() async {\n        let context = self.makeContext(sourceMode: .auto)\n        let strategy = ClaudeOAuthFetchStrategy()\n        let recordWithoutRequiredScope = ClaudeOAuthCredentialRecord(\n            credentials: ClaudeOAuthCredentials(\n                accessToken: \"expired-token\",\n                refreshToken: \"refresh-token\",\n                expiresAt: Date(timeIntervalSinceNow: -60),\n                scopes: [\"user:inference\"],\n                rateLimitTier: nil),\n            owner: .claudeCLI,\n            source: .cacheKeychain)\n\n        await KeychainAccessGate.withTaskOverrideForTesting(false) {\n            ClaudeOAuthKeychainAccessGate.resetForTesting()\n            defer { ClaudeOAuthKeychainAccessGate.resetForTesting() }\n\n            let now = Date(timeIntervalSince1970: 1000)\n            ClaudeOAuthKeychainAccessGate.recordDenied(now: now)\n            #expect(ClaudeOAuthKeychainAccessGate.shouldAllowPrompt(now: now) == false)\n\n            _ = await ClaudeOAuthFetchStrategy.$nonInteractiveCredentialRecordOverride\n                .withValue(recordWithoutRequiredScope) {\n                    await ProviderInteractionContext.$current.withValue(.userInitiated) {\n                        await strategy.isAvailable(context)\n                    }\n                }\n\n            #expect(ClaudeOAuthKeychainAccessGate.shouldAllowPrompt(now: now))\n        }\n    }\n\n    @Test\n    func `auto mode only on user action background startup without cache is available for bootstrap`() async throws {\n        let context = self.makeContext(sourceMode: .auto)\n        let strategy = ClaudeOAuthFetchStrategy()\n        let service = \"com.steipete.codexbar.cache.tests.\\(UUID().uuidString)\"\n\n        try await KeychainCacheStore.withServiceOverrideForTesting(service) {\n            KeychainCacheStore.setTestStoreForTesting(true)\n            defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n            try await ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting {\n                ClaudeOAuthCredentialsStore.invalidateCache()\n                ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                ClaudeOAuthKeychainAccessGate.resetForTesting()\n                defer {\n                    ClaudeOAuthCredentialsStore.invalidateCache()\n                    ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()\n                    ClaudeOAuthKeychainAccessGate.resetForTesting()\n                }\n\n                let tempDir = FileManager.default.temporaryDirectory\n                    .appendingPathComponent(UUID().uuidString, isDirectory: true)\n                try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n                let fileURL = tempDir.appendingPathComponent(\"credentials.json\")\n\n                let available = await ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {\n                    await ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(.securityFramework) {\n                        await ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.onlyOnUserAction) {\n                            await ProviderRefreshContext.$current.withValue(.startup) {\n                                await ProviderInteractionContext.$current.withValue(.background) {\n                                    await strategy.isAvailable(context)\n                                }\n                            }\n                        }\n                    }\n                }\n\n                #expect(available == true)\n            }\n        }\n    }\n\n    @Test\n    func `auto mode expired Claude CLI creds env provided CLI override returns available`() async throws {\n        let tempDir = FileManager.default.temporaryDirectory\n            .appendingPathComponent(UUID().uuidString, isDirectory: true)\n        try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n        let cliURL = tempDir.appendingPathComponent(\"claude\")\n        try Data(\"#!/bin/sh\\nexit 0\\n\".utf8).write(to: cliURL)\n        try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: cliURL.path)\n\n        let context = self.makeContext(\n            sourceMode: .auto,\n            env: [\"CLAUDE_CLI_PATH\": cliURL.path])\n        let strategy = ClaudeOAuthFetchStrategy()\n        let available = await ClaudeOAuthFetchStrategy.$nonInteractiveCredentialRecordOverride\n            .withValue(self.expiredRecord()) {\n                await strategy.isAvailable(context)\n            }\n\n        #expect(available == true)\n    }\n\n    @Test\n    func `auto mode experimental reader ignores prompt policy cooldown gate`() async {\n        let context = self.makeContext(sourceMode: .auto)\n        let strategy = ClaudeOAuthFetchStrategy()\n        let securityData = Data(\"\"\"\n        {\n          \"claudeAiOauth\": {\n            \"accessToken\": \"security-token\",\n            \"expiresAt\": \\(Int(Date(timeIntervalSinceNow: 3600).timeIntervalSince1970 * 1000)),\n            \"scopes\": [\"user:profile\"]\n          }\n        }\n        \"\"\".utf8)\n\n        let recordWithoutRequiredScope = ClaudeOAuthCredentialRecord(\n            credentials: ClaudeOAuthCredentials(\n                accessToken: \"token-no-scope\",\n                refreshToken: \"refresh-token\",\n                expiresAt: Date(timeIntervalSinceNow: -60),\n                scopes: [\"user:inference\"],\n                rateLimitTier: nil),\n            owner: .claudeCLI,\n            source: .cacheKeychain)\n\n        let available = await KeychainAccessGate.withTaskOverrideForTesting(false) {\n            await ClaudeOAuthKeychainAccessGate.withShouldAllowPromptOverrideForTesting(false) {\n                await ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(\n                    .securityCLIExperimental)\n                {\n                    await ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.onlyOnUserAction) {\n                        await ClaudeOAuthFetchStrategy.$nonInteractiveCredentialRecordOverride.withValue(\n                            recordWithoutRequiredScope)\n                        {\n                            await ProviderInteractionContext.$current.withValue(.background) {\n                                await ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting(.data(\n                                    securityData))\n                                {\n                                    await strategy.isAvailable(context)\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        #expect(available == true)\n    }\n\n    @Test\n    func `auto mode experimental reader security failure blocks availability when stored policy blocks fallback`()\n        async\n    {\n        let context = self.makeContext(sourceMode: .auto)\n        let strategy = ClaudeOAuthFetchStrategy()\n        let fallbackData = Data(\"\"\"\n        {\n          \"claudeAiOauth\": {\n            \"accessToken\": \"fallback-token\",\n            \"expiresAt\": \\(Int(Date(timeIntervalSinceNow: 3600).timeIntervalSince1970 * 1000)),\n            \"scopes\": [\"user:profile\"]\n          }\n        }\n        \"\"\".utf8)\n\n        let recordWithoutRequiredScope = ClaudeOAuthCredentialRecord(\n            credentials: ClaudeOAuthCredentials(\n                accessToken: \"token-no-scope\",\n                refreshToken: \"refresh-token\",\n                expiresAt: Date(timeIntervalSinceNow: -60),\n                scopes: [\"user:inference\"],\n                rateLimitTier: nil),\n            owner: .claudeCLI,\n            source: .cacheKeychain)\n\n        let available = await KeychainAccessGate.withTaskOverrideForTesting(false) {\n            await ClaudeOAuthKeychainAccessGate.withShouldAllowPromptOverrideForTesting(true) {\n                await ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(\n                    .securityCLIExperimental)\n                {\n                    await ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.onlyOnUserAction) {\n                        await ClaudeOAuthFetchStrategy.$nonInteractiveCredentialRecordOverride.withValue(\n                            recordWithoutRequiredScope)\n                        {\n                            await ProviderInteractionContext.$current.withValue(.background) {\n                                await ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(\n                                    data: fallbackData,\n                                    fingerprint: nil)\n                                {\n                                    await ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting(\n                                        .nonZeroExit)\n                                    {\n                                        await strategy.isAvailable(context)\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        #expect(available == false)\n    }\n}\n#endif\n"
  },
  {
    "path": "Tests/CodexBarTests/ClaudeOAuthKeychainAccessGateTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\n@Suite(.serialized)\nstruct ClaudeOAuthKeychainAccessGateTests {\n    @Test\n    func `blocks until cooldown expires`() {\n        KeychainAccessGate.withTaskOverrideForTesting(false) {\n            let store = ClaudeOAuthKeychainAccessGate.DeniedUntilStore()\n            ClaudeOAuthKeychainAccessGate.withDeniedUntilStoreOverrideForTesting(store) {\n                let now = Date(timeIntervalSince1970: 1000)\n                #expect(ClaudeOAuthKeychainAccessGate.shouldAllowPrompt(now: now))\n\n                ClaudeOAuthKeychainAccessGate.recordDenied(now: now)\n                #expect(ClaudeOAuthKeychainAccessGate.shouldAllowPrompt(now: now) == false)\n                #expect(\n                    ClaudeOAuthKeychainAccessGate.shouldAllowPrompt(now: now.addingTimeInterval(60 * 60 * 6 - 1))\n                        == false)\n                #expect(ClaudeOAuthKeychainAccessGate.shouldAllowPrompt(now: now.addingTimeInterval(60 * 60 * 6 + 1)))\n            }\n        }\n    }\n\n    @Test\n    func `persists denied until`() {\n        KeychainAccessGate.withTaskOverrideForTesting(false) {\n            ClaudeOAuthKeychainAccessGate.resetForTesting()\n            defer { ClaudeOAuthKeychainAccessGate.resetForTesting() }\n\n            let now = Date(timeIntervalSince1970: 2000)\n            ClaudeOAuthKeychainAccessGate.recordDenied(now: now)\n\n            ClaudeOAuthKeychainAccessGate.resetInMemoryForTesting()\n\n            #expect(\n                ClaudeOAuthKeychainAccessGate.shouldAllowPrompt(now: now.addingTimeInterval(60 * 60 * 6 - 1)) == false)\n        }\n    }\n\n    @Test\n    func `respects debug disable keychain access`() {\n        KeychainAccessGate.withTaskOverrideForTesting(true) {\n            ClaudeOAuthKeychainAccessGate.resetForTesting()\n            defer { ClaudeOAuthKeychainAccessGate.resetForTesting() }\n            #expect(ClaudeOAuthKeychainAccessGate.shouldAllowPrompt(now: Date()) == false)\n        }\n    }\n\n    @Test\n    func `clear denied allows immediate retry`() {\n        KeychainAccessGate.withTaskOverrideForTesting(false) {\n            ClaudeOAuthKeychainAccessGate.resetForTesting()\n            defer { ClaudeOAuthKeychainAccessGate.resetForTesting() }\n\n            let store = ClaudeOAuthKeychainAccessGate.DeniedUntilStore()\n            ClaudeOAuthKeychainAccessGate.withDeniedUntilStoreOverrideForTesting(store) {\n                let now = Date(timeIntervalSince1970: 3000)\n                ClaudeOAuthKeychainAccessGate.recordDenied(now: now)\n                #expect(ClaudeOAuthKeychainAccessGate.shouldAllowPrompt(now: now) == false)\n\n                #expect(ClaudeOAuthKeychainAccessGate.clearDenied(now: now))\n                #expect(ClaudeOAuthKeychainAccessGate.shouldAllowPrompt(now: now))\n                #expect(ClaudeOAuthKeychainAccessGate.clearDenied(now: now) == false)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/ClaudeOAuthRefreshDispositionTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\nstruct ClaudeOAuthRefreshDispositionTests {\n    @Test\n    func `invalid grant is terminal`() {\n        let data = Data(#\"{\"error\":\"invalid_grant\"}\"#.utf8)\n        #expect(ClaudeOAuthCredentialsStore\n            .refreshFailureDispositionForTesting(statusCode: 400, data: data) == \"terminalInvalidGrant\")\n    }\n\n    @Test\n    func `other error is transient`() {\n        let data = Data(#\"{\"error\":\"invalid_request\"}\"#.utf8)\n        #expect(ClaudeOAuthCredentialsStore\n            .refreshFailureDispositionForTesting(statusCode: 400, data: data) == \"transientBackoff\")\n    }\n\n    @Test\n    func `undecodable body is transient`() {\n        let data = Data(\"not-json\".utf8)\n        #expect(ClaudeOAuthCredentialsStore\n            .refreshFailureDispositionForTesting(statusCode: 401, data: data) == \"transientBackoff\")\n        #expect(ClaudeOAuthCredentialsStore.extractOAuthErrorCodeForTesting(from: data) == nil)\n    }\n\n    @Test\n    func `non auth status is not handled`() {\n        let data = Data(#\"{\"error\":\"invalid_grant\"}\"#.utf8)\n        #expect(ClaudeOAuthCredentialsStore.refreshFailureDispositionForTesting(statusCode: 500, data: data) == nil)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/ClaudeOAuthRefreshFailureGateTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\n#if os(macOS)\n@Suite(.serialized)\nstruct ClaudeOAuthRefreshFailureGateTests {\n    private let legacyBlockedUntilKey = \"claudeOAuthRefreshBackoffBlockedUntilV1\"\n    private let legacyFailureCountKey = \"claudeOAuthRefreshBackoffFailureCountV1\"\n    private let legacyFingerprintKey = \"claudeOAuthRefreshBackoffFingerprintV2\"\n    private let terminalBlockedKey = \"claudeOAuthRefreshTerminalBlockedV1\"\n    private let transientBlockedUntilKey = \"claudeOAuthRefreshTransientBlockedUntilV1\"\n    private let transientFailureCountKey = \"claudeOAuthRefreshTransientFailureCountV1\"\n\n    @Test\n    func `blocks indefinitely when fingerprint unchanged`() {\n        ClaudeOAuthRefreshFailureGate.resetForTesting()\n        defer { ClaudeOAuthRefreshFailureGate.resetForTesting() }\n\n        var fingerprint = ClaudeOAuthRefreshFailureGate.AuthFingerprint(\n            keychain: ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                modifiedAt: 1,\n                createdAt: 1,\n                persistentRefHash: \"ref1\"),\n            credentialsFile: \"file1\")\n        ClaudeOAuthRefreshFailureGate.setFingerprintProviderOverrideForTesting { fingerprint }\n        defer { ClaudeOAuthRefreshFailureGate.setFingerprintProviderOverrideForTesting(nil) }\n\n        let start = Date(timeIntervalSince1970: 1000)\n        ClaudeOAuthRefreshFailureGate.recordTerminalAuthFailure(now: start)\n\n        #expect(ClaudeOAuthRefreshFailureGate.shouldAttempt(now: start.addingTimeInterval(60)) == false)\n\n        // Ensure we do not get unblocked unless fingerprint changes.\n        fingerprint = ClaudeOAuthRefreshFailureGate.AuthFingerprint(\n            keychain: ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                modifiedAt: 1,\n                createdAt: 1,\n                persistentRefHash: \"ref1\"),\n            credentialsFile: \"file1\")\n        #expect(ClaudeOAuthRefreshFailureGate.shouldAttempt(now: start.addingTimeInterval(60 * 4)) == false)\n        #expect(ClaudeOAuthRefreshFailureGate.shouldAttempt(now: start.addingTimeInterval(60 * 60 * 24)) == false)\n    }\n\n    @Test\n    func `migrates legacy blocked until in past does not block and clears key`() {\n        ClaudeOAuthRefreshFailureGate.resetForTesting()\n        defer { ClaudeOAuthRefreshFailureGate.resetForTesting() }\n\n        let now = Date(timeIntervalSince1970: 10000)\n        UserDefaults.standard.set(now.addingTimeInterval(-60).timeIntervalSince1970, forKey: self.legacyBlockedUntilKey)\n        UserDefaults.standard.set(0, forKey: self.legacyFailureCountKey)\n        UserDefaults.standard.removeObject(forKey: self.terminalBlockedKey)\n\n        #expect(ClaudeOAuthRefreshFailureGate.shouldAttempt(now: now) == true)\n        #expect(UserDefaults.standard.object(forKey: self.legacyBlockedUntilKey) == nil)\n    }\n\n    @Test\n    func `migrates legacy backoff to transient backoff does not set terminal block`() throws {\n        ClaudeOAuthRefreshFailureGate.resetForTesting()\n        defer { ClaudeOAuthRefreshFailureGate.resetForTesting() }\n\n        let now = Date(timeIntervalSince1970: 20000)\n\n        let fingerprint = ClaudeOAuthRefreshFailureGate.AuthFingerprint(\n            keychain: ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                modifiedAt: 1,\n                createdAt: 1,\n                persistentRefHash: \"ref1\"),\n            credentialsFile: \"file1\")\n        ClaudeOAuthRefreshFailureGate.setFingerprintProviderOverrideForTesting { fingerprint }\n        defer { ClaudeOAuthRefreshFailureGate.setFingerprintProviderOverrideForTesting(nil) }\n\n        let legacyBlockedUntil = now.addingTimeInterval(60 * 10)\n        UserDefaults.standard.set(2, forKey: self.legacyFailureCountKey)\n        UserDefaults.standard.removeObject(forKey: self.terminalBlockedKey)\n        UserDefaults.standard.set(legacyBlockedUntil.timeIntervalSince1970, forKey: self.legacyBlockedUntilKey)\n        let data = try JSONEncoder().encode(fingerprint)\n        UserDefaults.standard.set(data, forKey: self.legacyFingerprintKey)\n\n        #expect(ClaudeOAuthRefreshFailureGate.shouldAttempt(now: now) == false)\n        #expect(UserDefaults.standard.bool(forKey: self.terminalBlockedKey) == false)\n        #expect(UserDefaults.standard.object(forKey: self.legacyBlockedUntilKey) == nil)\n        #expect(UserDefaults.standard.object(forKey: self.transientBlockedUntilKey) != nil)\n        #expect(UserDefaults.standard.integer(forKey: self.transientFailureCountKey) == 2)\n    }\n\n    @Test\n    func `unblocks when fingerprint becomes available after being unknown at failure`() {\n        ClaudeOAuthRefreshFailureGate.resetForTesting()\n        defer { ClaudeOAuthRefreshFailureGate.resetForTesting() }\n\n        var fingerprint: ClaudeOAuthRefreshFailureGate.AuthFingerprint?\n        ClaudeOAuthRefreshFailureGate.setFingerprintProviderOverrideForTesting { fingerprint }\n        defer { ClaudeOAuthRefreshFailureGate.setFingerprintProviderOverrideForTesting(nil) }\n\n        let start = Date(timeIntervalSince1970: 25000)\n        ClaudeOAuthRefreshFailureGate.recordTerminalAuthFailure(now: start)\n\n        // Still blocked while fingerprint is unavailable.\n        #expect(ClaudeOAuthRefreshFailureGate.shouldAttempt(now: start.addingTimeInterval(20)) == false)\n\n        // Once fingerprint becomes available, the sentinel differs and we unblock.\n        fingerprint = ClaudeOAuthRefreshFailureGate.AuthFingerprint(\n            keychain: ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                modifiedAt: 1,\n                createdAt: 1,\n                persistentRefHash: \"ref1\"),\n            credentialsFile: \"file1\")\n        #expect(ClaudeOAuthRefreshFailureGate.shouldAttempt(now: start.addingTimeInterval(40)) == true)\n    }\n\n    @Test\n    func `unblocks immediately when fingerprint changes`() {\n        ClaudeOAuthRefreshFailureGate.resetForTesting()\n        defer { ClaudeOAuthRefreshFailureGate.resetForTesting() }\n\n        var fingerprint = ClaudeOAuthRefreshFailureGate.AuthFingerprint(\n            keychain: ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                modifiedAt: 1,\n                createdAt: 1,\n                persistentRefHash: \"ref1\"),\n            credentialsFile: \"file1\")\n        ClaudeOAuthRefreshFailureGate.setFingerprintProviderOverrideForTesting { fingerprint }\n        defer { ClaudeOAuthRefreshFailureGate.setFingerprintProviderOverrideForTesting(nil) }\n\n        let start = Date(timeIntervalSince1970: 2000)\n        ClaudeOAuthRefreshFailureGate.recordTerminalAuthFailure(now: start)\n        #expect(ClaudeOAuthRefreshFailureGate.shouldAttempt(now: start.addingTimeInterval(60)) == false)\n\n        fingerprint = ClaudeOAuthRefreshFailureGate.AuthFingerprint(\n            keychain: ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                modifiedAt: 2,\n                createdAt: 2,\n                persistentRefHash: \"ref2\"),\n            credentialsFile: \"file2\")\n        #expect(ClaudeOAuthRefreshFailureGate.shouldAttempt(now: start.addingTimeInterval(60 * 2)) == true)\n    }\n\n    @Test\n    func `throttles fingerprint recheck while terminal blocked`() {\n        ClaudeOAuthRefreshFailureGate.resetForTesting()\n        defer { ClaudeOAuthRefreshFailureGate.resetForTesting() }\n\n        var calls = 0\n        let fingerprint = ClaudeOAuthRefreshFailureGate.AuthFingerprint(\n            keychain: ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                modifiedAt: 1,\n                createdAt: 1,\n                persistentRefHash: \"ref1\"),\n            credentialsFile: \"file1\")\n        ClaudeOAuthRefreshFailureGate.setFingerprintProviderOverrideForTesting {\n            calls += 1\n            return fingerprint\n        }\n        defer { ClaudeOAuthRefreshFailureGate.setFingerprintProviderOverrideForTesting(nil) }\n\n        let start = Date(timeIntervalSince1970: 30000)\n        ClaudeOAuthRefreshFailureGate.recordTerminalAuthFailure(now: start)\n        #expect(calls == 1)\n\n        // First blocked check is throttled (we already captured fingerprint at failure).\n        #expect(ClaudeOAuthRefreshFailureGate.shouldAttempt(now: start.addingTimeInterval(1)) == false)\n        #expect(calls == 1)\n\n        // After the throttle window, it should re-read.\n        #expect(ClaudeOAuthRefreshFailureGate.shouldAttempt(now: start.addingTimeInterval(20)) == false)\n        #expect(calls == 2)\n\n        // Subsequent checks within the throttle window should not re-read again.\n        #expect(ClaudeOAuthRefreshFailureGate.shouldAttempt(now: start.addingTimeInterval(21)) == false)\n        #expect(calls == 2)\n    }\n\n    @Test\n    func `terminal block is monotonic when transient failure is recorded`() {\n        ClaudeOAuthRefreshFailureGate.resetForTesting()\n        defer { ClaudeOAuthRefreshFailureGate.resetForTesting() }\n\n        let fingerprint = ClaudeOAuthRefreshFailureGate.AuthFingerprint(\n            keychain: ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                modifiedAt: 1,\n                createdAt: 1,\n                persistentRefHash: \"ref1\"),\n            credentialsFile: \"file1\")\n        ClaudeOAuthRefreshFailureGate.setFingerprintProviderOverrideForTesting { fingerprint }\n        defer { ClaudeOAuthRefreshFailureGate.setFingerprintProviderOverrideForTesting(nil) }\n\n        let start = Date(timeIntervalSince1970: 35000)\n        ClaudeOAuthRefreshFailureGate.recordTerminalAuthFailure(now: start)\n        ClaudeOAuthRefreshFailureGate.recordTransientFailure(now: start.addingTimeInterval(1))\n\n        #expect(ClaudeOAuthRefreshFailureGate.shouldAttempt(now: start.addingTimeInterval(20)) == false)\n        #expect(UserDefaults.standard.bool(forKey: self.terminalBlockedKey) == true)\n        #expect(UserDefaults.standard.object(forKey: self.transientBlockedUntilKey) == nil)\n    }\n\n    @Test\n    func `record success clears terminal block`() {\n        ClaudeOAuthRefreshFailureGate.resetForTesting()\n        defer { ClaudeOAuthRefreshFailureGate.resetForTesting() }\n\n        let fingerprint = ClaudeOAuthRefreshFailureGate.AuthFingerprint(\n            keychain: ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                modifiedAt: 1,\n                createdAt: 1,\n                persistentRefHash: \"ref1\"),\n            credentialsFile: \"file1\")\n        ClaudeOAuthRefreshFailureGate.setFingerprintProviderOverrideForTesting { fingerprint }\n        defer { ClaudeOAuthRefreshFailureGate.setFingerprintProviderOverrideForTesting(nil) }\n\n        let start = Date(timeIntervalSince1970: 5000)\n        ClaudeOAuthRefreshFailureGate.recordTerminalAuthFailure(now: start)\n        #expect(ClaudeOAuthRefreshFailureGate.shouldAttempt(now: start.addingTimeInterval(60)) == false)\n\n        ClaudeOAuthRefreshFailureGate.recordSuccess()\n        #expect(ClaudeOAuthRefreshFailureGate.shouldAttempt(now: start.addingTimeInterval(60)) == true)\n    }\n\n    @Test\n    func `transient backoff blocks until expiry then unblocks`() {\n        ClaudeOAuthRefreshFailureGate.resetForTesting()\n        defer { ClaudeOAuthRefreshFailureGate.resetForTesting() }\n\n        let fingerprint = ClaudeOAuthRefreshFailureGate.AuthFingerprint(\n            keychain: ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                modifiedAt: 1,\n                createdAt: 1,\n                persistentRefHash: \"ref1\"),\n            credentialsFile: \"file1\")\n        ClaudeOAuthRefreshFailureGate.setFingerprintProviderOverrideForTesting { fingerprint }\n        defer { ClaudeOAuthRefreshFailureGate.setFingerprintProviderOverrideForTesting(nil) }\n\n        let start = Date(timeIntervalSince1970: 60000)\n        ClaudeOAuthRefreshFailureGate.recordTransientFailure(now: start)\n\n        #expect(ClaudeOAuthRefreshFailureGate.shouldAttempt(now: start.addingTimeInterval(1)) == false)\n        #expect(ClaudeOAuthRefreshFailureGate.shouldAttempt(now: start.addingTimeInterval(60 * 5 - 1)) == false)\n        #expect(ClaudeOAuthRefreshFailureGate.shouldAttempt(now: start.addingTimeInterval(60 * 5 + 1)) == true)\n    }\n\n    @Test\n    func `transient backoff is exponential and capped`() {\n        ClaudeOAuthRefreshFailureGate.resetForTesting()\n        defer { ClaudeOAuthRefreshFailureGate.resetForTesting() }\n\n        let fingerprint = ClaudeOAuthRefreshFailureGate.AuthFingerprint(\n            keychain: ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                modifiedAt: 1,\n                createdAt: 1,\n                persistentRefHash: \"ref1\"),\n            credentialsFile: \"file1\")\n        ClaudeOAuthRefreshFailureGate.setFingerprintProviderOverrideForTesting { fingerprint }\n        defer { ClaudeOAuthRefreshFailureGate.setFingerprintProviderOverrideForTesting(nil) }\n\n        let start = Date(timeIntervalSince1970: 70000)\n        ClaudeOAuthRefreshFailureGate.recordTransientFailure(now: start)\n        // Second failure before the first window expires should double the backoff.\n        let secondFailureAt = start.addingTimeInterval(1)\n        ClaudeOAuthRefreshFailureGate.recordTransientFailure(now: secondFailureAt)\n        #expect(ClaudeOAuthRefreshFailureGate\n            .shouldAttempt(now: secondFailureAt.addingTimeInterval(60 * 10 - 1)) == false)\n        #expect(ClaudeOAuthRefreshFailureGate\n            .shouldAttempt(now: secondFailureAt.addingTimeInterval(60 * 10 + 1)) == true)\n\n        ClaudeOAuthRefreshFailureGate.resetInMemoryStateForTesting()\n        for _ in 0..<20 {\n            ClaudeOAuthRefreshFailureGate.recordTransientFailure(now: start)\n        }\n\n        #expect(ClaudeOAuthRefreshFailureGate.shouldAttempt(now: start.addingTimeInterval(60 * 60 * 6 - 1)) == false)\n        #expect(ClaudeOAuthRefreshFailureGate.shouldAttempt(now: start.addingTimeInterval(60 * 60 * 6 + 1)) == true)\n    }\n\n    @Test\n    func `transient backoff unblocks early when fingerprint changes`() {\n        ClaudeOAuthRefreshFailureGate.resetForTesting()\n        defer { ClaudeOAuthRefreshFailureGate.resetForTesting() }\n\n        var fingerprint = ClaudeOAuthRefreshFailureGate.AuthFingerprint(\n            keychain: ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                modifiedAt: 1,\n                createdAt: 1,\n                persistentRefHash: \"ref1\"),\n            credentialsFile: \"file1\")\n        ClaudeOAuthRefreshFailureGate.setFingerprintProviderOverrideForTesting { fingerprint }\n        defer { ClaudeOAuthRefreshFailureGate.setFingerprintProviderOverrideForTesting(nil) }\n\n        let start = Date(timeIntervalSince1970: 80000)\n        ClaudeOAuthRefreshFailureGate.recordTransientFailure(now: start)\n\n        // Still blocked while timer is active and fingerprint unchanged.\n        #expect(ClaudeOAuthRefreshFailureGate.shouldAttempt(now: start.addingTimeInterval(20)) == false)\n\n        fingerprint = ClaudeOAuthRefreshFailureGate.AuthFingerprint(\n            keychain: ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(\n                modifiedAt: 2,\n                createdAt: 2,\n                persistentRefHash: \"ref2\"),\n            credentialsFile: \"file2\")\n\n        // Even though the 5-minute cooldown window hasn't elapsed, a fingerprint change should unblock.\n        #expect(ClaudeOAuthRefreshFailureGate.shouldAttempt(now: start.addingTimeInterval(40)) == true)\n    }\n}\n#endif\n"
  },
  {
    "path": "Tests/CodexBarTests/ClaudeOAuthTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\nstruct ClaudeOAuthTests {\n    @Test\n    func `parses O auth credentials`() throws {\n        let json = \"\"\"\n        {\n          \"claudeAiOauth\": {\n            \"accessToken\": \"test-token\",\n            \"refreshToken\": \"test-refresh\",\n            \"expiresAt\": 4102444800000,\n            \"scopes\": [\"usage:read\"],\n            \"rateLimitTier\": \"default_claude_max_20x\"\n          }\n        }\n        \"\"\"\n        let creds = try ClaudeOAuthCredentials.parse(data: Data(json.utf8))\n        #expect(creds.accessToken == \"test-token\")\n        #expect(creds.refreshToken == \"test-refresh\")\n        #expect(creds.scopes == [\"usage:read\"])\n        #expect(creds.rateLimitTier == \"default_claude_max_20x\")\n        #expect(creds.isExpired == false)\n    }\n\n    @Test\n    func `missing access token throws`() {\n        let json = \"\"\"\n        {\n          \"claudeAiOauth\": {\n            \"accessToken\": \"\",\n            \"refreshToken\": \"test-refresh\",\n            \"expiresAt\": 1735689600000\n          }\n        }\n        \"\"\"\n        #expect(throws: ClaudeOAuthCredentialsError.self) {\n            _ = try ClaudeOAuthCredentials.parse(data: Data(json.utf8))\n        }\n    }\n\n    @Test\n    func `missing O auth block throws`() {\n        let json = \"\"\"\n        { \"other\": { \"accessToken\": \"nope\" } }\n        \"\"\"\n        #expect(throws: ClaudeOAuthCredentialsError.self) {\n            _ = try ClaudeOAuthCredentials.parse(data: Data(json.utf8))\n        }\n    }\n\n    @Test\n    func `treats missing expiry as expired`() {\n        let creds = ClaudeOAuthCredentials(\n            accessToken: \"token\",\n            refreshToken: nil,\n            expiresAt: nil,\n            scopes: [],\n            rateLimitTier: nil)\n        #expect(creds.isExpired == true)\n    }\n\n    @Test\n    func `maps O auth usage to snapshot`() throws {\n        let json = \"\"\"\n        {\n          \"five_hour\": { \"utilization\": 12.5, \"resets_at\": \"2025-12-25T12:00:00.000Z\" },\n          \"seven_day\": { \"utilization\": 30, \"resets_at\": \"2025-12-31T00:00:00.000Z\" },\n          \"seven_day_sonnet\": { \"utilization\": 5 }\n        }\n        \"\"\"\n        let snap = try ClaudeUsageFetcher._mapOAuthUsageForTesting(\n            Data(json.utf8),\n            rateLimitTier: \"claude_pro\")\n        #expect(snap.primary.usedPercent == 12.5)\n        #expect(snap.primary.windowMinutes == 300)\n        #expect(snap.secondary?.usedPercent == 30)\n        #expect(snap.opus?.usedPercent == 5)\n        #expect(snap.primary.resetsAt != nil)\n        #expect(snap.loginMethod == \"Claude Pro\")\n    }\n\n    @Test\n    func `maps O auth extra usage`() throws {\n        // OAuth API returns values in cents (minor units), same as Web API.\n        // The normalization always converts to dollars (major units).\n        let json = \"\"\"\n        {\n          \"five_hour\": { \"utilization\": 1, \"resets_at\": \"2025-12-25T12:00:00.000Z\" },\n          \"extra_usage\": {\n            \"is_enabled\": true,\n            \"monthly_limit\": 2050,\n            \"used_credits\": 325\n          }\n        }\n        \"\"\"\n        let snap = try ClaudeUsageFetcher._mapOAuthUsageForTesting(Data(json.utf8))\n        #expect(snap.providerCost?.currencyCode == \"USD\")\n        #expect(snap.providerCost?.limit == 20.5)\n        #expect(snap.providerCost?.used == 3.25)\n    }\n\n    @Test\n    func `maps O auth extra usage minor units as major units`() throws {\n        let json = \"\"\"\n        {\n          \"five_hour\": { \"utilization\": 1, \"resets_at\": \"2025-12-25T12:00:00.000Z\" },\n          \"extra_usage\": {\n            \"is_enabled\": true,\n            \"monthly_limit\": 2000,\n            \"used_credits\": 520,\n            \"currency\": \"USD\"\n          }\n        }\n        \"\"\"\n        let snap = try ClaudeUsageFetcher._mapOAuthUsageForTesting(Data(json.utf8))\n        #expect(snap.providerCost?.currencyCode == \"USD\")\n        #expect(snap.providerCost?.limit == 20)\n        #expect(snap.providerCost?.used == 5.2)\n    }\n\n    @Test\n    func `normalizes high limit O auth extra usage`() throws {\n        let json = \"\"\"\n        {\n          \"five_hour\": { \"utilization\": 1, \"resets_at\": \"2025-12-25T12:00:00.000Z\" },\n          \"extra_usage\": {\n            \"is_enabled\": true,\n            \"monthly_limit\": 200000,\n            \"used_credits\": 22200,\n            \"currency\": \"USD\"\n          }\n        }\n        \"\"\"\n        let snap = try ClaudeUsageFetcher._mapOAuthUsageForTesting(\n            Data(json.utf8),\n            rateLimitTier: \"claude_pro\")\n        #expect(snap.providerCost?.currencyCode == \"USD\")\n        #expect(snap.providerCost?.limit == 2000)\n        #expect(snap.providerCost?.used == 222)\n    }\n\n    @Test\n    func `normalizes O auth extra usage cents to major units`() throws {\n        let json = \"\"\"\n        {\n          \"five_hour\": { \"utilization\": 1, \"resets_at\": \"2025-12-25T12:00:00.000Z\" },\n          \"extra_usage\": {\n            \"is_enabled\": true,\n            \"monthly_limit\": 200000,\n            \"used_credits\": 22200,\n            \"currency\": \"USD\"\n          }\n        }\n        \"\"\"\n        let snap = try ClaudeUsageFetcher._mapOAuthUsageForTesting(Data(json.utf8))\n        #expect(snap.providerCost?.currencyCode == \"USD\")\n        #expect(snap.providerCost?.limit == 2000)\n        #expect(snap.providerCost?.used == 222)\n    }\n\n    @Test\n    func `prefers opus when sonnet missing`() throws {\n        let json = \"\"\"\n        {\n          \"five_hour\": { \"utilization\": 10, \"resets_at\": \"2025-12-25T12:00:00.000Z\" },\n          \"seven_day_opus\": { \"utilization\": 42 }\n        }\n        \"\"\"\n        let snap = try ClaudeUsageFetcher._mapOAuthUsageForTesting(Data(json.utf8))\n        #expect(snap.opus?.usedPercent == 42)\n    }\n\n    @Test\n    func `includes body in O auth403 error`() {\n        let err = ClaudeOAuthFetchError.serverError(\n            403,\n            \"HTTP 403: OAuth token does not meet scope requirement user:profile\")\n        #expect(err.localizedDescription.contains(\"user:profile\"))\n        #expect(err.localizedDescription.contains(\"HTTP 403\"))\n    }\n\n    @Test\n    func `oauth usage user agent uses claude code version`() {\n        #expect(\n            ClaudeOAuthUsageFetcher._userAgentForTesting(versionString: \"2.1.70 (Claude Code)\")\n                == \"claude-code/2.1.70\")\n        #expect(ClaudeOAuthUsageFetcher._userAgentForTesting(versionString: nil) == \"claude-code/2.1.0\")\n    }\n\n    @Test\n    func `skips extra usage when disabled`() throws {\n        let json = \"\"\"\n        {\n          \"five_hour\": { \"utilization\": 1, \"resets_at\": \"2025-12-25T12:00:00.000Z\" },\n          \"extra_usage\": {\n            \"is_enabled\": false,\n            \"monthly_limit\": 100,\n            \"used_credits\": 10\n          }\n        }\n        \"\"\"\n        let snap = try ClaudeUsageFetcher._mapOAuthUsageForTesting(Data(json.utf8))\n        #expect(snap.providerCost == nil)\n    }\n\n    // MARK: - Scope-based strategy resolution\n\n    @Test\n    func `prefers O auth when available`() {\n        let strategy = ClaudeProviderDescriptor.resolveUsageStrategy(\n            selectedDataSource: .auto,\n            webExtrasEnabled: false,\n            hasWebSession: true,\n            hasCLI: true,\n            hasOAuthCredentials: true)\n        #expect(strategy.dataSource == .oauth)\n    }\n\n    @Test\n    func `falls back to CLI when O auth missing and CLI available`() {\n        let strategy = ClaudeProviderDescriptor.resolveUsageStrategy(\n            selectedDataSource: .auto,\n            webExtrasEnabled: false,\n            hasWebSession: true,\n            hasCLI: true,\n            hasOAuthCredentials: false)\n        #expect(strategy.dataSource == .cli)\n    }\n\n    @Test\n    func `falls back to web when O auth missing and CLI missing`() {\n        let strategy = ClaudeProviderDescriptor.resolveUsageStrategy(\n            selectedDataSource: .auto,\n            webExtrasEnabled: false,\n            hasWebSession: true,\n            hasCLI: false,\n            hasOAuthCredentials: false)\n        #expect(strategy.dataSource == .web)\n    }\n\n    @Test\n    func `falls back to CLI when O auth missing and web missing`() {\n        let strategy = ClaudeProviderDescriptor.resolveUsageStrategy(\n            selectedDataSource: .auto,\n            webExtrasEnabled: false,\n            hasWebSession: false,\n            hasCLI: true,\n            hasOAuthCredentials: false)\n        #expect(strategy.dataSource == .cli)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/ClaudePlanResolverTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\nstruct ClaudePlanResolverTests {\n    @Test\n    func `oauth rate limit tier maps to branded plan`() {\n        #expect(ClaudePlan.oauthLoginMethod(rateLimitTier: \"default_claude_max_20x\") == \"Claude Max\")\n        #expect(ClaudePlan.oauthLoginMethod(rateLimitTier: \"claude_pro\") == \"Claude Pro\")\n        #expect(ClaudePlan.oauthLoginMethod(rateLimitTier: \"claude_team\") == \"Claude Team\")\n        #expect(ClaudePlan.oauthLoginMethod(rateLimitTier: \"claude_enterprise\") == \"Claude Enterprise\")\n    }\n\n    @Test\n    func `web fallback preserves stripe Claude compatibility`() {\n        #expect(\n            ClaudePlan.webLoginMethod(\n                rateLimitTier: \"default_claude\",\n                billingType: \"stripe_subscription\")\n                == \"Claude Pro\")\n    }\n\n    @Test\n    func `compatibility parser understands current labels`() {\n        #expect(ClaudePlan.fromCompatibilityLoginMethod(\"Claude Max\") == .max)\n        #expect(ClaudePlan.fromCompatibilityLoginMethod(\"Max\") == .max)\n        #expect(ClaudePlan.fromCompatibilityLoginMethod(\"Claude Pro\") == .pro)\n        #expect(ClaudePlan.fromCompatibilityLoginMethod(\"Ultra\") == .ultra)\n        #expect(ClaudePlan.fromCompatibilityLoginMethod(\"Claude Team\") == .team)\n        #expect(ClaudePlan.fromCompatibilityLoginMethod(\"Claude Enterprise\") == .enterprise)\n    }\n\n    @Test\n    func `CLI projection keeps compact compatibility and unknown fallback`() {\n        #expect(ClaudePlan.cliCompatibilityLoginMethod(\"Claude Max Account\") == \"Max\")\n        #expect(ClaudePlan.cliCompatibilityLoginMethod(\"Team\") == \"Team\")\n        #expect(ClaudePlan.cliCompatibilityLoginMethod(\"Claude Enterprise Account\") == \"Enterprise\")\n        #expect(ClaudePlan.cliCompatibilityLoginMethod(\"Claude Ultra Account\") == \"Ultra\")\n        #expect(ClaudePlan.cliCompatibilityLoginMethod(\"Experimental\") == \"Experimental\")\n        #expect(ClaudePlan.cliCompatibilityLoginMethod(\"Profile\") == \"Profile\")\n        #expect(ClaudePlan.cliCompatibilityLoginMethod(\"Browser profile\") == \"Browser profile\")\n    }\n\n    @Test\n    func `subscription compatibility preserves ultra and excludes enterprise`() {\n        #expect(ClaudePlan.isSubscriptionLoginMethod(\"Claude Max\"))\n        #expect(ClaudePlan.isSubscriptionLoginMethod(\"Pro\"))\n        #expect(ClaudePlan.isSubscriptionLoginMethod(\"Ultra\"))\n        #expect(ClaudePlan.isSubscriptionLoginMethod(\"Team\"))\n        #expect(!ClaudePlan.isSubscriptionLoginMethod(\"Claude Enterprise\"))\n        #expect(!ClaudePlan.isSubscriptionLoginMethod(\"Profile\"))\n        #expect(!ClaudePlan.isSubscriptionLoginMethod(\"Browser profile\"))\n        #expect(!ClaudePlan.isSubscriptionLoginMethod(\"API\"))\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/ClaudeResilienceTests.swift",
    "content": "import Testing\n@testable import CodexBar\n\nstruct ClaudeResilienceTests {\n    @Test\n    func `suppresses single flake when prior data exists`() {\n        var gate = ConsecutiveFailureGate()\n        let firstFailure = gate.shouldSurfaceError(onFailureWithPriorData: true)\n        let secondFailure = gate.shouldSurfaceError(onFailureWithPriorData: true)\n        #expect(firstFailure == false)\n        #expect(secondFailure == true)\n    }\n\n    @Test\n    func `surfaces failure without prior data`() {\n        var gate = ConsecutiveFailureGate()\n        let shouldSurface = gate.shouldSurfaceError(onFailureWithPriorData: false)\n        #expect(shouldSurface)\n    }\n\n    @Test\n    func `resets after success`() {\n        var gate = ConsecutiveFailureGate()\n        _ = gate.shouldSurfaceError(onFailureWithPriorData: true)\n        gate.recordSuccess()\n        let shouldSurface = gate.shouldSurfaceError(onFailureWithPriorData: true)\n        #expect(shouldSurface == false)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/ClaudeSourcePlannerTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\nstruct ClaudeSourcePlannerTests {\n    @Test\n    func `app auto plan preserves ordered steps and reasons`() {\n        let plan = ClaudeSourcePlanner.resolve(input: ClaudeSourcePlanningInput(\n            runtime: .app,\n            selectedDataSource: .auto,\n            webExtrasEnabled: false,\n            hasWebSession: true,\n            hasCLI: true,\n            hasOAuthCredentials: true))\n\n        #expect(plan.orderedSteps.map(\\.dataSource) == [.oauth, .cli, .web])\n        #expect(plan.orderedSteps.map(\\.inclusionReason) == [\n            .appAutoPreferredOAuth,\n            .appAutoFallbackCLI,\n            .appAutoFallbackWeb,\n        ])\n        #expect(plan.availableSteps.map(\\.dataSource) == [.oauth, .cli, .web])\n        #expect(plan.preferredStep?.dataSource == .oauth)\n    }\n\n    @Test\n    func `CLI auto plan preserves ordered steps and reasons`() {\n        let plan = ClaudeSourcePlanner.resolve(input: ClaudeSourcePlanningInput(\n            runtime: .cli,\n            selectedDataSource: .auto,\n            webExtrasEnabled: false,\n            hasWebSession: true,\n            hasCLI: true,\n            hasOAuthCredentials: false))\n\n        #expect(plan.orderedSteps.map(\\.dataSource) == [.web, .cli])\n        #expect(plan.orderedSteps.map(\\.inclusionReason) == [\n            .cliAutoPreferredWeb,\n            .cliAutoFallbackCLI,\n        ])\n        #expect(plan.preferredStep?.dataSource == .web)\n    }\n\n    @Test\n    func `explicit mode plan is single step`() {\n        let plan = ClaudeSourcePlanner.resolve(input: ClaudeSourcePlanningInput(\n            runtime: .app,\n            selectedDataSource: .cli,\n            webExtrasEnabled: true,\n            hasWebSession: false,\n            hasCLI: true,\n            hasOAuthCredentials: false))\n\n        #expect(plan.orderedSteps.count == 1)\n        #expect(plan.orderedSteps.first?.dataSource == .cli)\n        #expect(plan.orderedSteps.first?.inclusionReason == .explicitSourceSelection)\n        #expect(plan.compatibilityStrategy == ClaudeUsageStrategy(dataSource: .cli, useWebExtras: true))\n    }\n\n    @Test\n    func `app auto CLI fallback reports web extras like runtime`() {\n        let plan = ClaudeSourcePlanner.resolve(input: ClaudeSourcePlanningInput(\n            runtime: .app,\n            selectedDataSource: .auto,\n            webExtrasEnabled: true,\n            hasWebSession: false,\n            hasCLI: true,\n            hasOAuthCredentials: false))\n\n        #expect(plan.preferredStep?.dataSource == .cli)\n        #expect(plan.compatibilityStrategy == ClaudeUsageStrategy(dataSource: .cli, useWebExtras: true))\n    }\n\n    @Test\n    func `no source planner output is deterministic`() {\n        let input = ClaudeSourcePlanningInput(\n            runtime: .app,\n            selectedDataSource: .auto,\n            webExtrasEnabled: false,\n            hasWebSession: false,\n            hasCLI: false,\n            hasOAuthCredentials: false)\n        let plan = ClaudeSourcePlanner.resolve(input: input)\n\n        #expect(plan.orderedSteps.map(\\.dataSource) == [.oauth, .cli, .web])\n        #expect(plan.availableSteps.isEmpty)\n        #expect(plan.isNoSourceAvailable)\n        #expect(plan.preferredStep == nil)\n        #expect(plan.executionSteps.isEmpty)\n        #expect(plan.debugLines() == [\n            \"planner_order=oauth→cli→web\",\n            \"planner_selected=none\",\n            \"planner_no_source=true\",\n            \"planner_step.oauth=unavailable reason=app-auto-preferred-oauth\",\n            \"planner_step.cli=unavailable reason=app-auto-fallback-cli\",\n            \"planner_step.web=unavailable reason=app-auto-fallback-web\",\n        ])\n    }\n\n    @Test\n    func `CLI resolver falls back to PATH when Claude CLI path override is invalid`() throws {\n        let tempDir = FileManager.default.temporaryDirectory\n            .appendingPathComponent(UUID().uuidString, isDirectory: true)\n        try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n        let binaryURL = tempDir.appendingPathComponent(\"claude\")\n        try Data(\"#!/bin/sh\\nexit 0\\n\".utf8).write(to: binaryURL)\n        try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: binaryURL.path)\n\n        let resolved = ClaudeCLIResolver.resolvedBinaryPath(\n            environment: [\n                \"CLAUDE_CLI_PATH\": \"/definitely/missing/claude\",\n                \"PATH\": tempDir.path,\n            ],\n            loginPATH: nil)\n\n        #expect(resolved == binaryURL.path)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/ClaudeUsageDelegatedRefreshEnvironmentTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\nstruct ClaudeUsageDelegatedRefreshEnvironmentTests {\n    @Test\n    func `oauth delegated retry passes fetcher environment to delegated refresh`() async throws {\n        let fetcher = ClaudeUsageFetcher(\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            environment: [\"CLAUDE_CLI_PATH\": \"/tmp/rat110-env-claude\"],\n            dataSource: .oauth,\n            oauthKeychainPromptCooldownEnabled: true)\n\n        let delegatedOverride: (@Sendable (Date, TimeInterval, [String: String]) async\n            -> ClaudeOAuthDelegatedRefreshCoordinator.Outcome)? = { _, _, environment in\n            #expect(environment[\"CLAUDE_CLI_PATH\"] == \"/tmp/rat110-env-claude\")\n            return .cliUnavailable\n        }\n        let loadCredsOverride: (@Sendable (\n            [String: String],\n            Bool,\n            Bool) async throws -> ClaudeOAuthCredentials)? = { _, _, _ in\n            throw ClaudeOAuthCredentialsError.refreshDelegatedToClaudeCLI\n        }\n\n        do {\n            _ = try await ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.onlyOnUserAction) {\n                try await ProviderInteractionContext.$current.withValue(.userInitiated) {\n                    try await ClaudeUsageFetcher.$delegatedRefreshAttemptOverride.withValue(\n                        delegatedOverride,\n                        operation: {\n                            try await ClaudeUsageFetcher.$loadOAuthCredentialsOverride.withValue(\n                                loadCredsOverride,\n                                operation: {\n                                    try await fetcher.loadLatestUsage(model: \"sonnet\")\n                                })\n                        })\n                }\n            }\n            Issue.record(\"Expected delegated retry to fail when the override reports CLI unavailable\")\n        } catch let error as ClaudeUsageError {\n            guard case let .oauthFailed(message) = error else {\n                Issue.record(\"Expected ClaudeUsageError.oauthFailed, got \\(error)\")\n                return\n            }\n            #expect(message.contains(\"Claude CLI is not available\"))\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/ClaudeUsageTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBar\n@testable import CodexBarCore\n\nstruct ClaudeUsageTests {\n    private actor AsyncCounter {\n        private var value = 0\n\n        func increment() -> Int {\n            self.value += 1\n            return self.value\n        }\n\n        func current() -> Int {\n            self.value\n        }\n    }\n\n    private static func makeOAuthUsageResponse() throws -> OAuthUsageResponse {\n        let json = \"\"\"\n        {\n          \"five_hour\": { \"utilization\": 7, \"resets_at\": \"2025-12-23T16:00:00.000Z\" },\n          \"seven_day\": { \"utilization\": 21, \"resets_at\": \"2025-12-29T23:00:00.000Z\" }\n        }\n        \"\"\"\n        return try ClaudeOAuthUsageFetcher._decodeUsageResponseForTesting(Data(json.utf8))\n    }\n\n    @Test\n    func `parses usage JSON with sonnet limit`() {\n        let json = \"\"\"\n        {\n          \"ok\": true,\n          \"session_5h\": { \"pct_used\": 1, \"resets\": \"11am (Europe/Vienna)\" },\n          \"week_all_models\": { \"pct_used\": 8, \"resets\": \"Nov 21 at 5am (Europe/Vienna)\" },\n          \"week_sonnet\": { \"pct_used\": 0, \"resets\": \"Nov 21 at 5am (Europe/Vienna)\" }\n        }\n        \"\"\"\n        let data = Data(json.utf8)\n        let snap = ClaudeUsageFetcher.parse(json: data)\n        #expect(snap != nil)\n        #expect(snap?.primary.usedPercent == 1)\n        #expect(snap?.secondary?.usedPercent == 8)\n        #expect(snap?.primary.resetDescription == \"11am (Europe/Vienna)\")\n    }\n\n    @Test\n    func `oauth delegated retry retries once then succeeds`() async throws {\n        let loadCounter = AsyncCounter()\n        let delegatedCounter = AsyncCounter()\n        let usageResponse = try Self.makeOAuthUsageResponse()\n        let fetcher = ClaudeUsageFetcher(\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            environment: [:],\n            dataSource: .oauth,\n            oauthKeychainPromptCooldownEnabled: true)\n\n        let fetchOverride: (@Sendable (String) async throws -> OAuthUsageResponse)? = { _ in usageResponse }\n        let delegatedOverride: (@Sendable (\n            Date,\n            TimeInterval,\n            [String: String]) async -> ClaudeOAuthDelegatedRefreshCoordinator.Outcome)? = { _, _, _ in\n            _ = await delegatedCounter.increment()\n            return .attemptedSucceeded\n        }\n        let loadCredsOverride: (@Sendable (\n            [String: String],\n            Bool,\n            Bool) async throws -> ClaudeOAuthCredentials)? = { _, _, _ in\n            let call = await loadCounter.increment()\n            if call == 1 {\n                throw ClaudeOAuthCredentialsError.refreshDelegatedToClaudeCLI\n            }\n            return ClaudeOAuthCredentials(\n                accessToken: \"fresh-token\",\n                refreshToken: \"refresh-token\",\n                expiresAt: Date(timeIntervalSinceNow: 3600),\n                scopes: [\"user:profile\"],\n                rateLimitTier: nil)\n        }\n\n        let snapshot = try await ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.onlyOnUserAction) {\n            try await ProviderInteractionContext.$current.withValue(.userInitiated) {\n                try await ClaudeUsageFetcher.$fetchOAuthUsageOverride.withValue(fetchOverride, operation: {\n                    try await ClaudeUsageFetcher.$delegatedRefreshAttemptOverride.withValue(\n                        delegatedOverride,\n                        operation: {\n                            try await ClaudeUsageFetcher.$loadOAuthCredentialsOverride\n                                .withValue(loadCredsOverride, operation: {\n                                    try await fetcher.loadLatestUsage(model: \"sonnet\")\n                                })\n                        })\n                })\n            }\n        }\n\n        #expect(await loadCounter.current() == 2)\n        #expect(await delegatedCounter.current() == 1)\n        #expect(snapshot.primary.usedPercent == 7)\n        #expect(snapshot.secondary?.usedPercent == 21)\n    }\n\n    @Test\n    func `oauth delegated retry second attempt still expired fails cleanly`() async throws {\n        let loadCounter = AsyncCounter()\n        let delegatedCounter = AsyncCounter()\n        let fetcher = ClaudeUsageFetcher(\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            environment: [:],\n            dataSource: .oauth,\n            oauthKeychainPromptCooldownEnabled: true)\n\n        do {\n            let delegatedOverride: (@Sendable (\n                Date,\n                TimeInterval,\n                [String: String]) async -> ClaudeOAuthDelegatedRefreshCoordinator.Outcome)? = { _, _, _ in\n                _ = await delegatedCounter.increment()\n                return .attemptedSucceeded\n            }\n            let loadCredsOverride: (@Sendable (\n                [String: String],\n                Bool,\n                Bool) async throws -> ClaudeOAuthCredentials)? = { _, _, _ in\n                _ = await loadCounter.increment()\n                throw ClaudeOAuthCredentialsError.refreshDelegatedToClaudeCLI\n            }\n\n            _ = try await ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.onlyOnUserAction) {\n                try await ProviderInteractionContext.$current.withValue(.userInitiated) {\n                    try await ClaudeUsageFetcher.$delegatedRefreshAttemptOverride.withValue(\n                        delegatedOverride,\n                        operation: {\n                            try await ClaudeUsageFetcher.$loadOAuthCredentialsOverride.withValue(\n                                loadCredsOverride,\n                                operation: {\n                                    try await fetcher.loadLatestUsage(model: \"sonnet\")\n                                })\n                        })\n                }\n            }\n            Issue.record(\"Expected delegated retry to fail when credentials remain expired\")\n        } catch let error as ClaudeUsageError {\n            guard case let .oauthFailed(message) = error else {\n                Issue.record(\"Expected ClaudeUsageError.oauthFailed, got \\(error)\")\n                return\n            }\n            #expect(message.contains(\"delegated Claude CLI refresh\"))\n        } catch {\n            Issue.record(\"Expected ClaudeUsageError, got \\(error)\")\n        }\n\n        #expect(await loadCounter.current() == 2)\n        #expect(await delegatedCounter.current() == 1)\n    }\n\n    @Test\n    func `oauth delegated retry auto mode cli unavailable fails fast`() async throws {\n        let loadCounter = AsyncCounter()\n        let delegatedCounter = AsyncCounter()\n\n        let fetcher = ClaudeUsageFetcher(\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            environment: [:],\n            dataSource: .oauth,\n            oauthKeychainPromptCooldownEnabled: true)\n\n        let delegatedOverride: (@Sendable (Date, TimeInterval, [String: String]) async\n            -> ClaudeOAuthDelegatedRefreshCoordinator.Outcome)? = { _, _, _ in\n            _ = await delegatedCounter.increment()\n            return .cliUnavailable\n        }\n        let loadCredsOverride: (@Sendable (\n            [String: String],\n            Bool,\n            Bool) async throws -> ClaudeOAuthCredentials)? = { _, _, _ in\n            _ = await loadCounter.increment()\n            throw ClaudeOAuthCredentialsError.refreshDelegatedToClaudeCLI\n        }\n\n        do {\n            _ = try await ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.onlyOnUserAction) {\n                try await ProviderInteractionContext.$current.withValue(.userInitiated) {\n                    try await ClaudeUsageFetcher.$delegatedRefreshAttemptOverride.withValue(\n                        delegatedOverride,\n                        operation: {\n                            try await ClaudeUsageFetcher.$loadOAuthCredentialsOverride.withValue(\n                                loadCredsOverride,\n                                operation: {\n                                    try await fetcher.loadLatestUsage(model: \"sonnet\")\n                                })\n                        })\n                }\n            }\n            Issue.record(\"Expected delegated retry to fail fast when CLI is unavailable\")\n        } catch let error as ClaudeUsageError {\n            guard case let .oauthFailed(message) = error else {\n                Issue.record(\"Expected ClaudeUsageError.oauthFailed, got \\(error)\")\n                return\n            }\n            #expect(message.contains(\"Claude CLI is not available\"))\n        } catch {\n            Issue.record(\"Expected ClaudeUsageError, got \\(error)\")\n        }\n\n        // Auto-mode: should not attempt a second credential load.\n        #expect(await loadCounter.current() == 1)\n        #expect(await delegatedCounter.current() == 1)\n    }\n\n    @Test\n    func `oauth delegated retry auto mode attempted failed then non interactive reload succeeds`() async throws {\n        let loadCounter = AsyncCounter()\n        let delegatedCounter = AsyncCounter()\n        let usageResponse = try Self.makeOAuthUsageResponse()\n\n        final class FlagBox: @unchecked Sendable {\n            var allowKeychainPromptFlags: [Bool] = []\n        }\n        let flags = FlagBox()\n\n        let fetcher = ClaudeUsageFetcher(\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            environment: [:],\n            dataSource: .oauth,\n            oauthKeychainPromptCooldownEnabled: true)\n\n        let fetchOverride: (@Sendable (String) async throws -> OAuthUsageResponse)? = { _ in usageResponse }\n        let delegatedOverride: (@Sendable (Date, TimeInterval, [String: String]) async\n            -> ClaudeOAuthDelegatedRefreshCoordinator.Outcome)? = { _, _, _ in\n            _ = await delegatedCounter.increment()\n            return .attemptedFailed(\"no-change\")\n        }\n        let loadCredsOverride: (@Sendable (\n            [String: String],\n            Bool,\n            Bool) async throws -> ClaudeOAuthCredentials)? = { _, allowKeychainPrompt, _ in\n            flags.allowKeychainPromptFlags.append(allowKeychainPrompt)\n            let call = await loadCounter.increment()\n            if call == 1 {\n                throw ClaudeOAuthCredentialsError.refreshDelegatedToClaudeCLI\n            }\n            return ClaudeOAuthCredentials(\n                accessToken: \"fresh-token\",\n                refreshToken: \"refresh-token\",\n                expiresAt: Date(timeIntervalSinceNow: 3600),\n                scopes: [\"user:profile\"],\n                rateLimitTier: nil)\n        }\n\n        let snapshot = try await ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.onlyOnUserAction) {\n            try await ProviderInteractionContext.$current.withValue(.userInitiated) {\n                try await ClaudeUsageFetcher.$fetchOAuthUsageOverride.withValue(fetchOverride, operation: {\n                    try await ClaudeUsageFetcher.$delegatedRefreshAttemptOverride.withValue(\n                        delegatedOverride,\n                        operation: {\n                            try await ClaudeUsageFetcher.$loadOAuthCredentialsOverride\n                                .withValue(loadCredsOverride, operation: {\n                                    try await fetcher.loadLatestUsage(model: \"sonnet\")\n                                })\n                        })\n                })\n            }\n        }\n\n        #expect(await loadCounter.current() == 2)\n        #expect(await delegatedCounter.current() == 1)\n        #expect(snapshot.primary.usedPercent == 7)\n\n        // User-initiated repair: if the delegated refresh couldn't sync silently, we may allow an interactive prompt\n        // on the retry to help recovery.\n        #expect(flags.allowKeychainPromptFlags.count == 2)\n        #expect(flags.allowKeychainPromptFlags[1] == true)\n    }\n\n    @Test\n    func `oauth delegated retry only on user action background suppresses delegation`() async throws {\n        let loadCounter = AsyncCounter()\n        let delegatedCounter = AsyncCounter()\n\n        let fetcher = ClaudeUsageFetcher(\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            environment: [:],\n            dataSource: .oauth,\n            oauthKeychainPromptCooldownEnabled: true)\n\n        let delegatedOverride: (@Sendable (\n            Date,\n            TimeInterval,\n            [String: String]) async -> ClaudeOAuthDelegatedRefreshCoordinator.Outcome)? = { _, _, _ in\n            _ = await delegatedCounter.increment()\n            return .attemptedSucceeded\n        }\n        let loadCredsOverride: (@Sendable (\n            [String: String],\n            Bool,\n            Bool) async throws -> ClaudeOAuthCredentials)? = { _, _, _ in\n            _ = await loadCounter.increment()\n            throw ClaudeOAuthCredentialsError.refreshDelegatedToClaudeCLI\n        }\n\n        do {\n            _ = try await ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.onlyOnUserAction) {\n                try await ProviderInteractionContext.$current.withValue(.background) {\n                    try await ClaudeUsageFetcher.$delegatedRefreshAttemptOverride.withValue(delegatedOverride) {\n                        try await ClaudeUsageFetcher.$loadOAuthCredentialsOverride.withValue(loadCredsOverride) {\n                            try await fetcher.loadLatestUsage(model: \"sonnet\")\n                        }\n                    }\n                }\n            }\n            Issue.record(\"Expected delegated refresh to be suppressed in background\")\n        } catch let error as ClaudeUsageError {\n            guard case let .oauthFailed(message) = error else {\n                Issue.record(\"Expected ClaudeUsageError.oauthFailed, got \\(error)\")\n                return\n            }\n            #expect(message.contains(\"background repair is suppressed\"))\n        } catch {\n            Issue.record(\"Expected ClaudeUsageError, got \\(error)\")\n        }\n\n        #expect(await loadCounter.current() == 1)\n        #expect(await delegatedCounter.current() == 0)\n    }\n\n    @Test\n    func `oauth delegated retry never background suppresses delegation even for CLI`() async throws {\n        let loadCounter = AsyncCounter()\n        let delegatedCounter = AsyncCounter()\n\n        let fetcher = ClaudeUsageFetcher(\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            environment: [:],\n            dataSource: .oauth,\n            oauthKeychainPromptCooldownEnabled: true,\n            allowBackgroundDelegatedRefresh: true)\n\n        let delegatedOverride: (@Sendable (\n            Date,\n            TimeInterval,\n            [String: String]) async -> ClaudeOAuthDelegatedRefreshCoordinator.Outcome)? = { _, _, _ in\n            _ = await delegatedCounter.increment()\n            return .attemptedSucceeded\n        }\n        let loadCredsOverride: (@Sendable (\n            [String: String],\n            Bool,\n            Bool) async throws -> ClaudeOAuthCredentials)? = { _, _, _ in\n            _ = await loadCounter.increment()\n            throw ClaudeOAuthCredentialsError.refreshDelegatedToClaudeCLI\n        }\n\n        do {\n            _ = try await ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.never) {\n                try await ProviderInteractionContext.$current.withValue(.background) {\n                    try await ClaudeUsageFetcher.$delegatedRefreshAttemptOverride.withValue(delegatedOverride) {\n                        try await ClaudeUsageFetcher.$loadOAuthCredentialsOverride.withValue(loadCredsOverride) {\n                            try await fetcher.loadLatestUsage(model: \"sonnet\")\n                        }\n                    }\n                }\n            }\n            Issue.record(\"Expected delegated refresh to be suppressed for prompt policy 'never'\")\n        } catch let error as ClaudeUsageError {\n            guard case let .oauthFailed(message) = error else {\n                Issue.record(\"Expected ClaudeUsageError.oauthFailed, got \\(error)\")\n                return\n            }\n            #expect(message.contains(\"Delegated refresh is disabled by 'never' keychain policy\"))\n        } catch {\n            Issue.record(\"Expected ClaudeUsageError, got \\(error)\")\n        }\n\n        #expect(await loadCounter.current() == 1)\n        #expect(await delegatedCounter.current() == 0)\n    }\n\n    @Test\n    func `oauth bootstrap only on user action background startup allows interactive read when no cache`() async throws {\n        final class FlagBox: @unchecked Sendable {\n            var allowKeychainPromptFlags: [Bool] = []\n        }\n\n        let flags = FlagBox()\n        let usageResponse = try Self.makeOAuthUsageResponse()\n        let fetcher = ClaudeUsageFetcher(\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            environment: [:],\n            dataSource: .oauth,\n            oauthKeychainPromptCooldownEnabled: true,\n            allowStartupBootstrapPrompt: true)\n\n        let fetchOverride: (@Sendable (String) async throws -> OAuthUsageResponse)? = { _ in usageResponse }\n        let loadCredsOverride: (@Sendable (\n            [String: String],\n            Bool,\n            Bool) async throws -> ClaudeOAuthCredentials)? = { _, allowKeychainPrompt, _ in\n            flags.allowKeychainPromptFlags.append(allowKeychainPrompt)\n            return ClaudeOAuthCredentials(\n                accessToken: \"fresh-token\",\n                refreshToken: \"refresh-token\",\n                expiresAt: Date(timeIntervalSinceNow: 3600),\n                scopes: [\"user:profile\"],\n                rateLimitTier: nil)\n        }\n\n        let snapshot = try await ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.onlyOnUserAction) {\n            try await ProviderRefreshContext.$current.withValue(.startup) {\n                try await ProviderInteractionContext.$current.withValue(.background) {\n                    try await ClaudeUsageFetcher.$hasCachedCredentialsOverride.withValue(false) {\n                        try await ClaudeUsageFetcher.$fetchOAuthUsageOverride.withValue(fetchOverride) {\n                            try await ClaudeUsageFetcher.$loadOAuthCredentialsOverride.withValue(loadCredsOverride) {\n                                try await fetcher.loadLatestUsage(model: \"sonnet\")\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        #expect(flags.allowKeychainPromptFlags == [true])\n        #expect(snapshot.primary.usedPercent == 7)\n    }\n\n    @Test\n    func `oauth delegated retry only on user action background allows delegation for CLI`() async throws {\n        let loadCounter = AsyncCounter()\n        let delegatedCounter = AsyncCounter()\n        let usageResponse = try Self.makeOAuthUsageResponse()\n\n        final class FlagBox: @unchecked Sendable {\n            var allowKeychainPromptFlags: [Bool] = []\n        }\n        let flags = FlagBox()\n\n        let fetcher = ClaudeUsageFetcher(\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            environment: [:],\n            dataSource: .oauth,\n            oauthKeychainPromptCooldownEnabled: false,\n            allowBackgroundDelegatedRefresh: true)\n\n        let fetchOverride: (@Sendable (String) async throws -> OAuthUsageResponse)? = { _ in usageResponse }\n        let delegatedOverride: (@Sendable (\n            Date,\n            TimeInterval,\n            [String: String]) async -> ClaudeOAuthDelegatedRefreshCoordinator.Outcome)? = { _, _, _ in\n            _ = await delegatedCounter.increment()\n            return .attemptedSucceeded\n        }\n        let loadCredsOverride: (@Sendable (\n            [String: String],\n            Bool,\n            Bool) async throws -> ClaudeOAuthCredentials)? = { _, allowKeychainPrompt, _ in\n            flags.allowKeychainPromptFlags.append(allowKeychainPrompt)\n            let call = await loadCounter.increment()\n            if call == 1 {\n                throw ClaudeOAuthCredentialsError.refreshDelegatedToClaudeCLI\n            }\n            return ClaudeOAuthCredentials(\n                accessToken: \"fresh-token\",\n                refreshToken: \"refresh-token\",\n                expiresAt: Date(timeIntervalSinceNow: 3600),\n                scopes: [\"user:profile\"],\n                rateLimitTier: nil)\n        }\n\n        let snapshot = try await ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.onlyOnUserAction) {\n            try await ProviderInteractionContext.$current.withValue(.background) {\n                try await ClaudeUsageFetcher.$fetchOAuthUsageOverride.withValue(fetchOverride) {\n                    try await ClaudeUsageFetcher.$delegatedRefreshAttemptOverride.withValue(delegatedOverride) {\n                        try await ClaudeUsageFetcher.$loadOAuthCredentialsOverride.withValue(loadCredsOverride) {\n                            try await fetcher.loadLatestUsage(model: \"sonnet\")\n                        }\n                    }\n                }\n            }\n        }\n\n        #expect(await loadCounter.current() == 2)\n        #expect(await delegatedCounter.current() == 1)\n        #expect(snapshot.primary.usedPercent == 7)\n        #expect(flags.allowKeychainPromptFlags.allSatisfy { !$0 })\n    }\n\n    @Test\n    func `parses usage JSON when weekly missing`() {\n        let json = \"\"\"\n        {\n          \"ok\": true,\n          \"session_5h\": { \"pct_used\": 4, \"resets\": \"11am (Europe/Vienna)\" }\n        }\n        \"\"\"\n        let data = Data(json.utf8)\n        let snap = ClaudeUsageFetcher.parse(json: data)\n        #expect(snap != nil)\n        #expect(snap?.primary.usedPercent == 4)\n        #expect(snap?.secondary == nil)\n    }\n\n    @Test\n    func `parses legacy opus and account`() {\n        let json = \"\"\"\n        {\n          \"ok\": true,\n          \"session_5h\": { \"pct_used\": 2, \"resets\": \"10:59pm (Europe/Vienna)\" },\n          \"week_all_models\": { \"pct_used\": 13, \"resets\": \"Nov 21 at 4:59am (Europe/Vienna)\" },\n          \"week_opus\": { \"pct_used\": 0, \"resets\": \"\" },\n          \"account_email\": \" steipete@gmail.com \",\n          \"account_org\": \"\"\n        }\n        \"\"\"\n        let data = Data(json.utf8)\n        let snap = ClaudeUsageFetcher.parse(json: data)\n        #expect(snap?.opus?.usedPercent == 0)\n        #expect(snap?.opus?.resetDescription?.isEmpty == true)\n        #expect(snap?.accountEmail == \"steipete@gmail.com\")\n        #expect(snap?.accountOrganization == nil)\n    }\n\n    @Test\n    func `parses usage JSON when only sonnet limit is present`() {\n        let json = \"\"\"\n        {\n          \"ok\": true,\n          \"session_5h\": { \"pct_used\": 3, \"resets\": \"11am (Europe/Vienna)\" },\n          \"week_all_models\": { \"pct_used\": 9, \"resets\": \"Nov 21 at 5am (Europe/Vienna)\" },\n          \"week_sonnet_only\": { \"pct_used\": 12, \"resets\": \"Nov 22 at 5am (Europe/Vienna)\" }\n        }\n        \"\"\"\n        let data = Data(json.utf8)\n        let snap = ClaudeUsageFetcher.parse(json: data)\n        #expect(snap?.secondary?.usedPercent == 9)\n        #expect(snap?.opus?.usedPercent == 12)\n        #expect(snap?.opus?.resetDescription == \"Nov 22 at 5am (Europe/Vienna)\")\n    }\n\n    @Test\n    func `trims account fields`() throws {\n        let cases: [[String: String?]] = [\n            [\"email\": \" steipete@gmail.com \", \"org\": \"  Org  \"],\n            [\"email\": \"\", \"org\": \" Claude Max Account \"],\n            [\"email\": nil, \"org\": \" \"],\n        ]\n\n        for entry in cases {\n            var payload = [\n                \"ok\": true,\n                \"session_5h\": [\"pct_used\": 0, \"resets\": \"\"],\n                \"week_all_models\": [\"pct_used\": 0, \"resets\": \"\"],\n            ] as [String: Any]\n            if let email = entry[\"email\"] { payload[\"account_email\"] = email }\n            if let org = entry[\"org\"] { payload[\"account_org\"] = org }\n            let data = try JSONSerialization.data(withJSONObject: payload)\n            let snap = ClaudeUsageFetcher.parse(json: data)\n            let emailRaw: String? = entry[\"email\"] ?? String?.none\n            let expectedEmail = emailRaw?.trimmingCharacters(in: .whitespacesAndNewlines)\n            let normalizedEmail = (expectedEmail?.isEmpty ?? true) ? nil : expectedEmail\n            #expect(snap?.accountEmail == normalizedEmail)\n            let orgRaw: String? = entry[\"org\"] ?? String?.none\n            let expectedOrg = orgRaw?.trimmingCharacters(in: .whitespacesAndNewlines)\n            let normalizedOrg = (expectedOrg?.isEmpty ?? true) ? nil : expectedOrg\n            #expect(snap?.accountOrganization == normalizedOrg)\n        }\n    }\n\n    @Test\n    func `live claude fetch PTY`() async throws {\n        guard ProcessInfo.processInfo.environment[\"LIVE_CLAUDE_FETCH\"] == \"1\" else {\n            return\n        }\n        let fetcher = ClaudeUsageFetcher(browserDetection: BrowserDetection(cacheTTL: 0), dataSource: .cli)\n        do {\n            let snap = try await fetcher.loadLatestUsage()\n            let opusUsed = snap.opus?.usedPercent ?? -1\n            let weeklyUsed = snap.secondary?.usedPercent ?? -1\n            let email = snap.accountEmail ?? \"nil\"\n            let org = snap.accountOrganization ?? \"nil\"\n            print(\n                \"\"\"\n                Live Claude usage (PTY):\n                session used \\(snap.primary.usedPercent)%\n                week used \\(weeklyUsed)% \n                opus \\(opusUsed)% \n                email \\(email) org \\(org)\n                \"\"\")\n            #expect(snap.primary.usedPercent >= 0)\n        } catch {\n            // Dump raw CLI text captured via `script` to help debug.\n            let raw = try Self.captureClaudeUsageRaw(timeout: 15)\n            print(\"RAW CLAUDE OUTPUT BEGIN\\n\\(raw)\\nRAW CLAUDE OUTPUT END\")\n            throw error\n        }\n    }\n\n    private static func captureClaudeUsageRaw(timeout: TimeInterval) throws -> String {\n        let process = Process()\n        process.launchPath = \"/usr/bin/script\"\n        process.arguments = [\n            \"-q\",\n            \"/dev/null\",\n            \"claude\",\n            \"/usage\",\n            \"--allowed-tools\",\n            \"\",\n        ]\n        let pipe = Pipe()\n        process.standardOutput = pipe\n        process.standardError = Pipe()\n        process.standardInput = nil\n\n        try process.run()\n        DispatchQueue.global().asyncAfter(deadline: .now() + timeout) {\n            if process.isRunning { process.terminate() }\n        }\n        process.waitUntilExit()\n\n        let data = pipe.fileHandleForReading.readDataToEndOfFile()\n        return String(data: data, encoding: .utf8) ?? \"\"\n    }\n\n    // MARK: - Web API tests\n\n    @Test\n    func `live claude fetch web API`() async throws {\n        // Set LIVE_CLAUDE_WEB_FETCH=1 to run this test with real browser cookies\n        guard ProcessInfo.processInfo.environment[\"LIVE_CLAUDE_WEB_FETCH\"] == \"1\" else {\n            return\n        }\n        let fetcher = ClaudeUsageFetcher(browserDetection: BrowserDetection(cacheTTL: 0), dataSource: .web)\n        let snap = try await fetcher.loadLatestUsage()\n        let weeklyUsed = snap.secondary?.usedPercent ?? -1\n        let opusUsed = snap.opus?.usedPercent ?? -1\n        print(\n            \"\"\"\n            Live Claude usage (Web API):\n            session used \\(snap.primary.usedPercent)%\n            week used \\(weeklyUsed)%\n            opus \\(opusUsed)%\n            login method: \\(snap.loginMethod ?? \"nil\")\n            \"\"\")\n        #expect(snap.primary.usedPercent >= 0)\n    }\n\n    @Test\n    func `claude web API has session key check`() {\n        // Quick check that hasSessionKey returns a boolean (doesn't crash)\n        let hasKey = ClaudeWebAPIFetcher.hasSessionKey(browserDetection: BrowserDetection(cacheTTL: 0))\n        // We can't assert the value since it depends on the test environment\n        #expect(hasKey == true || hasKey == false)\n    }\n\n    @Test\n    func `parses claude web API usage response`() throws {\n        let json = \"\"\"\n        {\n          \"five_hour\": { \"utilization\": 9, \"resets_at\": \"2025-12-23T16:00:00.000Z\" },\n          \"seven_day\": { \"utilization\": 4, \"resets_at\": \"2025-12-29T23:00:00.000Z\" },\n          \"seven_day_opus\": { \"utilization\": 1 }\n        }\n        \"\"\"\n        let data = Data(json.utf8)\n        let parsed = try ClaudeWebAPIFetcher._parseUsageResponseForTesting(data)\n        #expect(parsed.sessionPercentUsed == 9)\n        #expect(parsed.weeklyPercentUsed == 4)\n        #expect(parsed.opusPercentUsed == 1)\n        #expect(parsed.sessionResetsAt != nil)\n        #expect(parsed.weeklyResetsAt != nil)\n    }\n\n    @Test\n    func `parses claude web API usage response when weekly missing`() throws {\n        let json = \"\"\"\n        {\n          \"five_hour\": { \"utilization\": 9, \"resets_at\": \"2025-12-23T16:00:00.000Z\" }\n        }\n        \"\"\"\n        let data = Data(json.utf8)\n        let parsed = try ClaudeWebAPIFetcher._parseUsageResponseForTesting(data)\n        #expect(parsed.sessionPercentUsed == 9)\n        #expect(parsed.weeklyPercentUsed == nil)\n    }\n\n    @Test\n    func `parses claude web API overage spend limit`() {\n        let json = \"\"\"\n        {\n          \"monthly_credit_limit\": 2000,\n          \"currency\": \"EUR\",\n          \"used_credits\": 0,\n          \"is_enabled\": true\n        }\n        \"\"\"\n        let data = Data(json.utf8)\n        let cost = ClaudeWebAPIFetcher._parseOverageSpendLimitForTesting(data)\n        #expect(cost != nil)\n        #expect(cost?.currencyCode == \"EUR\")\n        #expect(cost?.limit == 20)\n        #expect(cost?.used == 0)\n        #expect(cost?.period == \"Monthly\")\n    }\n\n    @Test\n    func `parses claude web API overage spend limit cents`() {\n        let json = \"\"\"\n        {\n          \"monthly_credit_limit\": 12345,\n          \"currency\": \"USD\",\n          \"used_credits\": 6789,\n          \"is_enabled\": true\n        }\n        \"\"\"\n        let data = Data(json.utf8)\n        let cost = ClaudeWebAPIFetcher._parseOverageSpendLimitForTesting(data)\n        #expect(cost?.currencyCode == \"USD\")\n        #expect(cost?.limit == 123.45)\n        #expect(cost?.used == 67.89)\n    }\n\n    @Test\n    func `parses claude web API organizations response`() throws {\n        let json = \"\"\"\n        [\n          { \"uuid\": \"org-123\", \"name\": \"Example Org\", \"capabilities\": [] }\n        ]\n        \"\"\"\n        let data = Data(json.utf8)\n        let org = try ClaudeWebAPIFetcher._parseOrganizationsResponseForTesting(data)\n        #expect(org.id == \"org-123\")\n        #expect(org.name == \"Example Org\")\n    }\n\n    @Test\n    func `parses claude web API organizations prefers chat capability over api only`() throws {\n        let json = \"\"\"\n        [\n          { \"uuid\": \"org-api\", \"name\": \"API Org\", \"capabilities\": [\"api\"] },\n          { \"uuid\": \"org-chat\", \"name\": \"Chat Org\", \"capabilities\": [\"chat\"] }\n        ]\n        \"\"\"\n        let data = Data(json.utf8)\n        let org = try ClaudeWebAPIFetcher._parseOrganizationsResponseForTesting(data)\n        #expect(org.id == \"org-chat\")\n        #expect(org.name == \"Chat Org\")\n    }\n\n    @Test\n    func `parses claude web API organizations prefers hybrid chat org`() throws {\n        let json = \"\"\"\n        [\n          { \"uuid\": \"org-api\", \"name\": \"API Org\", \"capabilities\": [\"api\"] },\n          { \"uuid\": \"org-hybrid\", \"name\": \"Hybrid Org\", \"capabilities\": [\"api\", \"chat\"] }\n        ]\n        \"\"\"\n        let data = Data(json.utf8)\n        let org = try ClaudeWebAPIFetcher._parseOrganizationsResponseForTesting(data)\n        #expect(org.id == \"org-hybrid\")\n        #expect(org.name == \"Hybrid Org\")\n    }\n\n    @Test\n    func `parses claude web API account info`() {\n        let json = \"\"\"\n        {\n          \"email_address\": \"steipete@gmail.com\",\n          \"memberships\": [\n            {\n              \"organization\": {\n                \"uuid\": \"org-123\",\n                \"name\": \"Example Org\",\n                \"rate_limit_tier\": \"default_claude_max_20x\",\n                \"billing_type\": \"stripe_subscription\"\n              }\n            }\n          ]\n        }\n        \"\"\"\n        let data = Data(json.utf8)\n        let info = ClaudeWebAPIFetcher._parseAccountInfoForTesting(data, orgId: \"org-123\")\n        #expect(info?.email == \"steipete@gmail.com\")\n        #expect(info?.loginMethod == \"Claude Max\")\n    }\n\n    @Test\n    func `parses claude web API account info selects matching org`() {\n        let json = \"\"\"\n        {\n          \"email_address\": \"steipete@gmail.com\",\n          \"memberships\": [\n            {\n              \"organization\": {\n                \"uuid\": \"org-other\",\n                \"name\": \"Other Org\",\n                \"rate_limit_tier\": \"claude_pro\",\n                \"billing_type\": \"stripe_subscription\"\n              }\n            },\n            {\n              \"organization\": {\n                \"uuid\": \"org-123\",\n                \"name\": \"Example Org\",\n                \"rate_limit_tier\": \"claude_team\",\n                \"billing_type\": \"stripe_subscription\"\n              }\n            }\n          ]\n        }\n        \"\"\"\n        let data = Data(json.utf8)\n        let info = ClaudeWebAPIFetcher._parseAccountInfoForTesting(data, orgId: \"org-123\")\n        #expect(info?.loginMethod == \"Claude Team\")\n    }\n\n    @Test\n    func `parses claude web API account info falls back to first membership`() {\n        let json = \"\"\"\n        {\n          \"email_address\": \"steipete@gmail.com\",\n          \"memberships\": [\n            {\n              \"organization\": {\n                \"uuid\": \"org-first\",\n                \"name\": \"First Org\",\n                \"rate_limit_tier\": \"claude_enterprise\",\n                \"billing_type\": \"invoice\"\n              }\n            },\n            {\n              \"organization\": {\n                \"uuid\": \"org-second\",\n                \"name\": \"Second Org\",\n                \"rate_limit_tier\": \"claude_pro\",\n                \"billing_type\": \"stripe_subscription\"\n              }\n            }\n          ]\n        }\n        \"\"\"\n        let data = Data(json.utf8)\n        let info = ClaudeWebAPIFetcher._parseAccountInfoForTesting(data, orgId: nil)\n        #expect(info?.loginMethod == \"Claude Enterprise\")\n    }\n\n    @Test\n    func `claude usage fetcher init with data sources`() {\n        // Verify we can create fetchers with both configurations\n        let browserDetection = BrowserDetection(cacheTTL: 0)\n        let defaultFetcher = ClaudeUsageFetcher(browserDetection: browserDetection)\n        let webFetcher = ClaudeUsageFetcher(browserDetection: browserDetection, dataSource: .web)\n        let cliFetcher = ClaudeUsageFetcher(browserDetection: browserDetection, dataSource: .cli)\n        // Both should be valid instances (no crashes)\n        let defaultVersion = defaultFetcher.detectVersion()\n        let webVersion = webFetcher.detectVersion()\n        let cliVersion = cliFetcher.detectVersion()\n        #expect(defaultVersion?.isEmpty != true)\n        #expect(webVersion?.isEmpty != true)\n        #expect(cliVersion?.isEmpty != true)\n    }\n}\n\n@Suite(.serialized)\nstruct ClaudeAutoFetcherCharacterizationTests {\n    private final class RequestLog: @unchecked Sendable {\n        private var paths: [String] = []\n        private let lock = NSLock()\n\n        func append(_ path: String) {\n            self.lock.lock()\n            defer { self.lock.unlock() }\n            self.paths.append(path)\n        }\n\n        func current() -> [String] {\n            self.lock.lock()\n            defer { self.lock.unlock() }\n            return self.paths\n        }\n    }\n\n    private final class InvocationLog: @unchecked Sendable {\n        let url: URL\n\n        init(url: URL) {\n            self.url = url\n        }\n\n        func contents() -> String {\n            (try? String(contentsOf: self.url, encoding: .utf8)) ?? \"\"\n        }\n    }\n\n    private static func makeOAuthUsageResponse() throws -> OAuthUsageResponse {\n        let json = \"\"\"\n        {\n          \"five_hour\": { \"utilization\": 7, \"resets_at\": \"2025-12-23T16:00:00.000Z\" },\n          \"seven_day\": { \"utilization\": 21, \"resets_at\": \"2025-12-29T23:00:00.000Z\" }\n        }\n        \"\"\"\n        return try ClaudeOAuthUsageFetcher._decodeUsageResponseForTesting(Data(json.utf8))\n    }\n\n    private static func makeFakeClaudeCLI(logURL: URL) throws -> URL {\n        let directory = FileManager.default.temporaryDirectory\n            .appendingPathComponent(\"claude-auto-\\(UUID().uuidString)\", isDirectory: true)\n        try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)\n        let scriptURL = directory.appendingPathComponent(\"claude\")\n        let script = \"\"\"\n        #!/bin/sh\n        LOG_FILE='\\(logURL.path)'\n        while IFS= read -r line; do\n          case \"$line\" in\n            \"/usage\")\n              printf 'usage\\\\n' >> \"$LOG_FILE\"\n              cat <<'EOF'\n        Current session\n        93% left\n        Dec 23 at 4:00PM\n        Current week (all models)\n        79% left\n        Dec 29 at 11:00PM\n        EOF\n              ;;\n            \"/status\")\n              printf 'status\\\\n' >> \"$LOG_FILE\"\n              cat <<'EOF'\n        Account: cli@example.com\n        Org: CLI Org\n        EOF\n              ;;\n          esac\n        done\n        \"\"\"\n        try script.write(to: scriptURL, atomically: true, encoding: .utf8)\n        try FileManager.default.setAttributes(\n            [.posixPermissions: NSNumber(value: Int16(0o755))],\n            ofItemAtPath: scriptURL.path)\n        return scriptURL\n    }\n\n    private func withClaudeCLIPath<T>(_ path: String?, operation: () async throws -> T) async rethrows -> T {\n        let key = \"CLAUDE_CLI_PATH\"\n        let original = getenv(key).map { String(cString: $0) }\n        if let path {\n            setenv(key, path, 1)\n        } else {\n            unsetenv(key)\n        }\n        defer {\n            if let original {\n                setenv(key, original, 1)\n            } else {\n                unsetenv(key)\n            }\n        }\n        return try await operation()\n    }\n\n    private func withNoOAuthCredentials<T>(operation: () async throws -> T) async rethrows -> T {\n        let missingCredentialsURL = FileManager.default.temporaryDirectory\n            .appendingPathComponent(\"missing-claude-creds-\\(UUID().uuidString).json\")\n        return try await KeychainCacheStore.withServiceOverrideForTesting(\"rat-107-\\(UUID().uuidString)\") {\n            KeychainCacheStore.setTestStoreForTesting(true)\n            defer { KeychainCacheStore.setTestStoreForTesting(false) }\n            return try await ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting {\n                try await ClaudeOAuthCredentialsStore.withIsolatedCredentialsFileTrackingForTesting {\n                    try await ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(missingCredentialsURL) {\n                        try await ClaudeOAuthCredentialsStore.withKeychainAccessOverrideForTesting(false) {\n                            try await ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(\n                                data: nil,\n                                fingerprint: nil)\n                            {\n                                try await operation()\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    private func withClaudeWebStub<T>(\n        handler: @escaping (URLRequest) throws -> (HTTPURLResponse, Data),\n        operation: () async throws -> T) async rethrows -> T\n    {\n        let registered = URLProtocol.registerClass(ClaudeAutoFetcherStubURLProtocol.self)\n        ClaudeAutoFetcherStubURLProtocol.handler = handler\n        defer {\n            if registered {\n                URLProtocol.unregisterClass(ClaudeAutoFetcherStubURLProtocol.self)\n            }\n            ClaudeAutoFetcherStubURLProtocol.handler = nil\n        }\n        return try await operation()\n    }\n\n    private static func makeJSONResponse(\n        url: URL,\n        body: String,\n        statusCode: Int = 200) -> (HTTPURLResponse, Data)\n    {\n        let response = HTTPURLResponse(\n            url: url,\n            statusCode: statusCode,\n            httpVersion: \"HTTP/1.1\",\n            headerFields: [\"Content-Type\": \"application/json\"])!\n        return (response, Data(body.utf8))\n    }\n\n    @Test\n    func `auto prefers OAuth even when web and CLI appear available`() async throws {\n        let usageResponse = try Self.makeOAuthUsageResponse()\n        let cliLogURL = FileManager.default.temporaryDirectory\n            .appendingPathComponent(\"claude-auto-cli-log-\\(UUID().uuidString).txt\")\n        let log = InvocationLog(url: cliLogURL)\n        let fakeCLI = try Self.makeFakeClaudeCLI(logURL: cliLogURL)\n        let webRequests = RequestLog()\n        let fetcher = ClaudeUsageFetcher(\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            environment: [\n                ClaudeOAuthCredentialsStore.environmentTokenKey: \"oauth-token\",\n                ClaudeOAuthCredentialsStore.environmentScopesKey: \"user:profile\",\n            ],\n            runtime: .app,\n            dataSource: .auto,\n            manualCookieHeader: \"sessionKey=sk-ant-session-token\")\n\n        try await self.withClaudeCLIPath(fakeCLI.path) {\n            try await self.withClaudeWebStub(handler: { request in\n                webRequests.append(request.url?.path ?? \"<missing>\")\n                let url = try #require(request.url)\n                return Self.makeJSONResponse(url: url, body: \"{}\")\n            }, operation: {\n                let fetchOverride: @Sendable (String) async throws -> OAuthUsageResponse = { _ in usageResponse }\n                let snapshot = try await ClaudeUsageFetcher.$fetchOAuthUsageOverride.withValue(\n                    fetchOverride,\n                    operation: {\n                        try await fetcher.loadLatestUsage(model: \"sonnet\")\n                    })\n\n                #expect(snapshot.primary.usedPercent == 7)\n                #expect(snapshot.secondary?.usedPercent == 21)\n                #expect(log.contents().isEmpty)\n                let requests = webRequests.current()\n                #expect(requests.isEmpty)\n            })\n        }\n    }\n\n    @Test\n    func `app runtime auto prefers CLI before web when OAuth unavailable`() async throws {\n        let cliLogURL = FileManager.default.temporaryDirectory\n            .appendingPathComponent(\"claude-auto-web-log-\\(UUID().uuidString).txt\")\n        let log = InvocationLog(url: cliLogURL)\n        let fakeCLI = try Self.makeFakeClaudeCLI(logURL: cliLogURL)\n        let fetcher = ClaudeUsageFetcher(\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            environment: [\"CLAUDE_CLI_PATH\": fakeCLI.path],\n            runtime: .app,\n            dataSource: .auto,\n            manualCookieHeader: \"sessionKey=sk-ant-session-token\")\n\n        try await self.withClaudeCLIPath(fakeCLI.path) {\n            try await self.withNoOAuthCredentials {\n                try await self.withClaudeWebStub(handler: { request in\n                    let url = try #require(request.url)\n                    switch url.path {\n                    case \"/api/organizations\":\n                        return Self.makeJSONResponse(\n                            url: url,\n                            body: #\"[{\"uuid\":\"org-123\",\"name\":\"Test Org\",\"capabilities\":[\"chat\"]}]\"#)\n                    case \"/api/organizations/org-123/usage\":\n                        let body = \"\"\"\n                        {\n                          \"five_hour\": { \"utilization\": 11, \"resets_at\": \"2025-12-23T16:00:00.000Z\" },\n                          \"seven_day\": { \"utilization\": 22, \"resets_at\": \"2025-12-29T23:00:00.000Z\" },\n                          \"seven_day_opus\": { \"utilization\": 33 }\n                        }\n                        \"\"\"\n                        return Self.makeJSONResponse(\n                            url: url,\n                            body: body)\n                    case \"/api/account\":\n                        let body = \"\"\"\n                        {\n                          \"email_address\": \"web@example.com\",\n                          \"memberships\": [\n                            {\n                              \"organization\": {\n                                \"uuid\": \"org-123\",\n                                \"name\": \"Test Org\",\n                                \"rate_limit_tier\": \"claude_max\",\n                                \"billing_type\": \"stripe\"\n                              }\n                            }\n                          ]\n                        }\n                        \"\"\"\n                        return Self.makeJSONResponse(\n                            url: url,\n                            body: body)\n                    case \"/api/organizations/org-123/overage_spend_limit\":\n                        let body = \"\"\"\n                        {\"monthly_credit_limit\":5000,\"currency\":\"USD\",\"used_credits\":1200,\"is_enabled\":true}\n                        \"\"\"\n                        return Self.makeJSONResponse(\n                            url: url,\n                            body: body)\n                    default:\n                        return Self.makeJSONResponse(url: url, body: \"{}\", statusCode: 404)\n                    }\n                }, operation: {\n                    let snapshot = try await fetcher.loadLatestUsage(model: \"sonnet\")\n\n                    #expect(snapshot.rawText != nil)\n                    #expect(log.contents().contains(\"usage\"))\n                })\n            }\n        }\n    }\n\n    @Test\n    func `CLI runtime auto prefers web before CLI when OAuth unavailable`() async throws {\n        let cliLogURL = FileManager.default.temporaryDirectory\n            .appendingPathComponent(\"claude-auto-cli-runtime-web-log-\\(UUID().uuidString).txt\")\n        let log = InvocationLog(url: cliLogURL)\n        let fakeCLI = try Self.makeFakeClaudeCLI(logURL: cliLogURL)\n        let fetcher = ClaudeUsageFetcher(\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            environment: [\"CLAUDE_CLI_PATH\": fakeCLI.path],\n            runtime: .cli,\n            dataSource: .auto,\n            manualCookieHeader: \"sessionKey=sk-ant-session-token\")\n\n        try await self.withClaudeCLIPath(fakeCLI.path) {\n            try await self.withNoOAuthCredentials {\n                try await self.withClaudeWebStub(handler: { request in\n                    let url = try #require(request.url)\n                    switch url.path {\n                    case \"/api/organizations\":\n                        return Self.makeJSONResponse(\n                            url: url,\n                            body: #\"[{\"uuid\":\"org-123\",\"name\":\"Test Org\",\"capabilities\":[\"chat\"]}]\"#)\n                    case \"/api/organizations/org-123/usage\":\n                        let body = \"\"\"\n                        {\n                          \"five_hour\": { \"utilization\": 11, \"resets_at\": \"2025-12-23T16:00:00.000Z\" },\n                          \"seven_day\": { \"utilization\": 22, \"resets_at\": \"2025-12-29T23:00:00.000Z\" },\n                          \"seven_day_opus\": { \"utilization\": 33 }\n                        }\n                        \"\"\"\n                        return Self.makeJSONResponse(url: url, body: body)\n                    case \"/api/account\":\n                        let body = \"\"\"\n                        {\n                          \"email_address\": \"web@example.com\",\n                          \"memberships\": [\n                            {\n                              \"organization\": {\n                                \"uuid\": \"org-123\",\n                                \"name\": \"Test Org\",\n                                \"rate_limit_tier\": \"claude_max\",\n                                \"billing_type\": \"stripe\"\n                              }\n                            }\n                          ]\n                        }\n                        \"\"\"\n                        return Self.makeJSONResponse(url: url, body: body)\n                    case \"/api/organizations/org-123/overage_spend_limit\":\n                        let body = \"\"\"\n                        {\"monthly_credit_limit\":5000,\"currency\":\"USD\",\"used_credits\":1200,\"is_enabled\":true}\n                        \"\"\"\n                        return Self.makeJSONResponse(url: url, body: body)\n                    default:\n                        return Self.makeJSONResponse(url: url, body: \"{}\", statusCode: 404)\n                    }\n                }, operation: {\n                    let snapshot = try await fetcher.loadLatestUsage(model: \"sonnet\")\n\n                    #expect(snapshot.primary.usedPercent == 11)\n                    #expect(snapshot.secondary?.usedPercent == 22)\n                    #expect(snapshot.opus?.usedPercent == 33)\n                    #expect(snapshot.accountEmail == \"web@example.com\")\n                    #expect(snapshot.loginMethod == \"Claude Max\")\n                    #expect(log.contents().isEmpty)\n                })\n            }\n        }\n    }\n\n    @Test\n    func `app runtime auto fails deterministically when planner has no executable steps`() async {\n        let fetcher = ClaudeUsageFetcher(\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            environment: [\"CLAUDE_CLI_PATH\": \"/definitely/missing/claude\"],\n            runtime: .app,\n            dataSource: .auto,\n            manualCookieHeader: \"foo=bar\")\n\n        await self.withNoOAuthCredentials {\n            await ClaudeCLIResolver.withResolvedBinaryPathOverrideForTesting(\"/definitely/missing/claude\") {\n                do {\n                    _ = try await fetcher.loadLatestUsage(model: \"sonnet\")\n                    Issue.record(\"Expected app auto no-source fetch to fail.\")\n                } catch let error as ClaudeUsageError {\n                    #expect(error.localizedDescription.contains(\"Claude planner produced no executable steps.\"))\n                } catch {\n                    Issue.record(\"Unexpected error: \\(error)\")\n                }\n            }\n        }\n    }\n\n    @Test\n    func `CLI runtime auto fails deterministically when planner has no executable steps`() async {\n        let fetcher = ClaudeUsageFetcher(\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            environment: [\"CLAUDE_CLI_PATH\": \"/definitely/missing/claude\"],\n            runtime: .cli,\n            dataSource: .auto,\n            manualCookieHeader: \"foo=bar\")\n\n        await self.withNoOAuthCredentials {\n            await ClaudeCLIResolver.withResolvedBinaryPathOverrideForTesting(\"/definitely/missing/claude\") {\n                do {\n                    _ = try await fetcher.loadLatestUsage(model: \"sonnet\")\n                    Issue.record(\"Expected CLI auto no-source fetch to fail.\")\n                } catch let error as ClaudeUsageError {\n                    #expect(error.localizedDescription.contains(\"Claude planner produced no executable steps.\"))\n                } catch {\n                    Issue.record(\"Unexpected error: \\(error)\")\n                }\n            }\n        }\n    }\n}\n\nfinal class ClaudeAutoFetcherStubURLProtocol: URLProtocol {\n    nonisolated(unsafe) static var handler: ((URLRequest) throws -> (HTTPURLResponse, Data))?\n\n    override static func canInit(with request: URLRequest) -> Bool {\n        request.url?.host == \"claude.ai\"\n    }\n\n    override static func canonicalRequest(for request: URLRequest) -> URLRequest {\n        request\n    }\n\n    override func startLoading() {\n        guard let handler = Self.handler else {\n            self.client?.urlProtocol(self, didFailWithError: URLError(.badServerResponse))\n            return\n        }\n        do {\n            let (response, data) = try handler(self.request)\n            self.client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)\n            self.client?.urlProtocol(self, didLoad: data)\n            self.client?.urlProtocolDidFinishLoading(self)\n        } catch {\n            self.client?.urlProtocol(self, didFailWithError: error)\n        }\n    }\n\n    override func stopLoading() {}\n}\n\nextension ClaudeUsageTests {\n    @Test\n    func `oauth delegated retry experimental background ignores only on user action suppression`() async throws {\n        let loadCounter = AsyncCounter()\n        let delegatedCounter = AsyncCounter()\n        let usageResponse = try Self.makeOAuthUsageResponse()\n\n        let fetcher = ClaudeUsageFetcher(\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            environment: [:],\n            dataSource: .oauth,\n            oauthKeychainPromptCooldownEnabled: true,\n            allowBackgroundDelegatedRefresh: false)\n\n        let fetchOverride: (@Sendable (String) async throws -> OAuthUsageResponse)? = { _ in usageResponse }\n        let delegatedOverride: (@Sendable (\n            Date,\n            TimeInterval,\n            [String: String]) async -> ClaudeOAuthDelegatedRefreshCoordinator.Outcome)? = { _, _, _ in\n            _ = await delegatedCounter.increment()\n            return .attemptedSucceeded\n        }\n        let loadCredsOverride: (@Sendable (\n            [String: String],\n            Bool,\n            Bool) async throws -> ClaudeOAuthCredentials)? = { _, _, _ in\n            let call = await loadCounter.increment()\n            if call == 1 {\n                throw ClaudeOAuthCredentialsError.refreshDelegatedToClaudeCLI\n            }\n            return ClaudeOAuthCredentials(\n                accessToken: \"fresh-token\",\n                refreshToken: \"refresh-token\",\n                expiresAt: Date(timeIntervalSinceNow: 3600),\n                scopes: [\"user:profile\"],\n                rateLimitTier: nil)\n        }\n\n        let snapshot = try await ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(\n            .securityCLIExperimental,\n            operation: {\n                try await ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.onlyOnUserAction) {\n                    try await ProviderInteractionContext.$current.withValue(.background) {\n                        try await ClaudeUsageFetcher.$hasCachedCredentialsOverride.withValue(true) {\n                            try await ClaudeUsageFetcher.$fetchOAuthUsageOverride.withValue(fetchOverride) {\n                                try await ClaudeUsageFetcher.$delegatedRefreshAttemptOverride.withValue(\n                                    delegatedOverride)\n                                {\n                                    try await ClaudeUsageFetcher.$loadOAuthCredentialsOverride.withValue(\n                                        loadCredsOverride)\n                                    {\n                                        try await fetcher.loadLatestUsage(model: \"sonnet\")\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n            })\n\n        #expect(await loadCounter.current() == 2)\n        #expect(await delegatedCounter.current() == 1)\n        #expect(snapshot.primary.usedPercent == 7)\n    }\n\n    @Test\n    func `oauth load experimental background fallback blocked propagates O auth failure`() async throws {\n        final class FlagBox: @unchecked Sendable {\n            var respectPromptCooldownFlags: [Bool] = []\n        }\n        let flags = FlagBox()\n        let fetcher = ClaudeUsageFetcher(\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            environment: [:],\n            dataSource: .oauth,\n            oauthKeychainPromptCooldownEnabled: true,\n            allowBackgroundDelegatedRefresh: false)\n\n        let loadCredsOverride: (@Sendable (\n            [String: String],\n            Bool,\n            Bool) async throws -> ClaudeOAuthCredentials)? = { _, _, respectKeychainPromptCooldown in\n            flags.respectPromptCooldownFlags.append(respectKeychainPromptCooldown)\n            throw ClaudeOAuthCredentialsError.notFound\n        }\n\n        await #expect(throws: ClaudeUsageError.self) {\n            try await ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(\n                .securityCLIExperimental,\n                operation: {\n                    try await ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.onlyOnUserAction) {\n                        try await ProviderInteractionContext.$current.withValue(.background) {\n                            try await ClaudeUsageFetcher.$loadOAuthCredentialsOverride.withValue(loadCredsOverride) {\n                                try await fetcher.loadLatestUsage(model: \"sonnet\")\n                            }\n                        }\n                    }\n                })\n        }\n        #expect(flags.respectPromptCooldownFlags == [true])\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/CodexBarWidgetProviderTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n@testable import CodexBarWidget\n\nstruct CodexBarWidgetProviderTests {\n    @Test\n    func `provider choice supports alibaba`() {\n        #expect(ProviderChoice(provider: .alibaba) == .alibaba)\n        #expect(ProviderChoice.alibaba.provider == .alibaba)\n    }\n\n    @Test\n    func `supported providers keep alibaba when it is the only enabled provider`() {\n        let now = Date(timeIntervalSince1970: 1_700_000_000)\n        let entry = WidgetSnapshot.ProviderEntry(\n            provider: .alibaba,\n            updatedAt: now,\n            primary: nil,\n            secondary: nil,\n            tertiary: nil,\n            creditsRemaining: nil,\n            codeReviewRemainingPercent: nil,\n            tokenUsage: nil,\n            dailyUsage: [])\n        let snapshot = WidgetSnapshot(entries: [entry], enabledProviders: [.alibaba], generatedAt: now)\n\n        #expect(CodexBarSwitcherTimelineProvider.supportedProviders(from: snapshot) == [.alibaba])\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/CodexOAuthTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\nstruct CodexOAuthTests {\n    @Test\n    func `parses O auth credentials`() throws {\n        let json = \"\"\"\n        {\n          \"OPENAI_API_KEY\": null,\n          \"tokens\": {\n            \"access_token\": \"access-token\",\n            \"refresh_token\": \"refresh-token\",\n            \"id_token\": \"id-token\",\n            \"account_id\": \"account-123\"\n          },\n          \"last_refresh\": \"2025-12-20T12:34:56Z\"\n        }\n        \"\"\"\n        let creds = try CodexOAuthCredentialsStore.parse(data: Data(json.utf8))\n        #expect(creds.accessToken == \"access-token\")\n        #expect(creds.refreshToken == \"refresh-token\")\n        #expect(creds.idToken == \"id-token\")\n        #expect(creds.accountId == \"account-123\")\n        #expect(creds.lastRefresh != nil)\n    }\n\n    @Test\n    func `parses API key credentials`() throws {\n        let json = \"\"\"\n        {\n          \"OPENAI_API_KEY\": \"sk-test\"\n        }\n        \"\"\"\n        let creds = try CodexOAuthCredentialsStore.parse(data: Data(json.utf8))\n        #expect(creds.accessToken == \"sk-test\")\n        #expect(creds.refreshToken.isEmpty)\n        #expect(creds.idToken == nil)\n        #expect(creds.accountId == nil)\n    }\n\n    @Test\n    func `decodes credits balance string`() throws {\n        let json = \"\"\"\n        {\n          \"plan_type\": \"pro\",\n          \"rate_limit\": {\n            \"primary_window\": {\n              \"used_percent\": 12,\n              \"reset_at\": 1766948068,\n              \"limit_window_seconds\": 18000\n            }\n          },\n          \"credits\": {\n            \"has_credits\": false,\n            \"unlimited\": false,\n            \"balance\": \"0\"\n          }\n        }\n        \"\"\"\n        let response = try CodexOAuthUsageFetcher._decodeUsageResponseForTesting(Data(json.utf8))\n        #expect(response.planType?.rawValue == \"pro\")\n        #expect(response.credits?.balance == 0)\n        #expect(response.credits?.hasCredits == false)\n        #expect(response.credits?.unlimited == false)\n    }\n\n    @Test\n    func `maps usage windows from O auth`() throws {\n        let json = \"\"\"\n        {\n          \"rate_limit\": {\n            \"primary_window\": {\n              \"used_percent\": 22,\n              \"reset_at\": 1766948068,\n              \"limit_window_seconds\": 18000\n            },\n            \"secondary_window\": {\n              \"used_percent\": 43,\n              \"reset_at\": 1767407914,\n              \"limit_window_seconds\": 604800\n            }\n          }\n        }\n        \"\"\"\n        let creds = CodexOAuthCredentials(\n            accessToken: \"access\",\n            refreshToken: \"refresh\",\n            idToken: nil,\n            accountId: nil,\n            lastRefresh: Date())\n        let snapshot = try CodexOAuthFetchStrategy._mapUsageForTesting(Data(json.utf8), credentials: creds)\n        #expect(snapshot.primary?.usedPercent == 22)\n        #expect(snapshot.primary?.windowMinutes == 300)\n        #expect(snapshot.secondary?.usedPercent == 43)\n        #expect(snapshot.secondary?.windowMinutes == 10080)\n        #expect(snapshot.primary?.resetsAt != nil)\n        #expect(snapshot.secondary?.resetsAt != nil)\n    }\n\n    @Test\n    func `resolves chat GPT usage URL from config`() {\n        let config = \"chatgpt_base_url = \\\"https://chatgpt.com/backend-api/\\\"\\n\"\n        let url = CodexOAuthUsageFetcher._resolveUsageURLForTesting(configContents: config)\n        #expect(url.absoluteString == \"https://chatgpt.com/backend-api/wham/usage\")\n    }\n\n    @Test\n    func `resolves codex usage URL from config`() {\n        let config = \"chatgpt_base_url = \\\"https://api.openai.com\\\"\\n\"\n        let url = CodexOAuthUsageFetcher._resolveUsageURLForTesting(configContents: config)\n        #expect(url.absoluteString == \"https://api.openai.com/api/codex/usage\")\n    }\n\n    @Test\n    func `normalizes chat GPT base URL without backend API`() {\n        let config = \"chatgpt_base_url = \\\"https://chat.openai.com\\\"\\n\"\n        let url = CodexOAuthUsageFetcher._resolveUsageURLForTesting(configContents: config)\n        #expect(url.absoluteString == \"https://chat.openai.com/backend-api/wham/usage\")\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/CodexbarTests.swift",
    "content": "import AppKit\nimport CodexBarCore\nimport Foundation\nimport Testing\n@testable import CodexBar\n\nstruct CodexBarTests {\n    @Test\n    func `icon renderer produces template image`() {\n        let image = IconRenderer.makeIcon(\n            primaryRemaining: 50,\n            weeklyRemaining: 75,\n            creditsRemaining: 500,\n            stale: false,\n            style: .codex)\n        #expect(image.isTemplate)\n        #expect(image.size.width > 0)\n    }\n\n    @Test\n    func `icon renderer renders at pixel aligned size`() {\n        let image = IconRenderer.makeIcon(\n            primaryRemaining: 50,\n            weeklyRemaining: 75,\n            creditsRemaining: 500,\n            stale: false,\n            style: .claude)\n        let bitmapReps = image.representations.compactMap { $0 as? NSBitmapImageRep }\n        #expect(bitmapReps.contains { rep in\n            rep.pixelsWide == 36 && rep.pixelsHigh == 36\n        })\n    }\n\n    @Test\n    func `icon renderer caches static icons`() {\n        let first = IconRenderer.makeIcon(\n            primaryRemaining: 42,\n            weeklyRemaining: 17,\n            creditsRemaining: 250,\n            stale: false,\n            style: .codex)\n        let second = IconRenderer.makeIcon(\n            primaryRemaining: 42,\n            weeklyRemaining: 17,\n            creditsRemaining: 250,\n            stale: false,\n            style: .codex)\n        #expect(first === second)\n    }\n\n    @Test\n    func `icon renderer codex eyes punch through when unknown`() {\n        // Regression: when remaining is nil, CoreGraphics inherits the previous fill alpha which caused\n        // destinationOut “eyes” to become semi-transparent instead of fully punched through.\n        let image = IconRenderer.makeIcon(\n            primaryRemaining: nil,\n            weeklyRemaining: 1,\n            creditsRemaining: nil,\n            stale: false,\n            style: .codex)\n\n        let bitmapReps = image.representations.compactMap { $0 as? NSBitmapImageRep }\n        let rep = bitmapReps.first(where: { $0.pixelsWide == 36 && $0.pixelsHigh == 36 })\n        #expect(rep != nil)\n        guard let rep else { return }\n\n        func alphaAt(px x: Int, _ y: Int) -> CGFloat {\n            (rep.colorAt(x: x, y: y) ?? .clear).alphaComponent\n        }\n\n        let w = rep.pixelsWide\n        let h = rep.pixelsHigh\n        let isTransparent: (Int, Int) -> Bool = { x, y in\n            alphaAt(px: x, y) < 0.05\n        }\n\n        // Flood-fill from the border through transparent pixels to label the \"outside\".\n        var visited = Array(repeating: Array(repeating: false, count: w), count: h)\n        var queue: [(Int, Int)] = []\n        queue.reserveCapacity(w * 2 + h * 2)\n\n        func enqueueIfOutside(_ x: Int, _ y: Int) {\n            guard x >= 0, x < w, y >= 0, y < h else { return }\n            guard !visited[y][x], isTransparent(x, y) else { return }\n            visited[y][x] = true\n            queue.append((x, y))\n        }\n\n        for x in 0..<w {\n            enqueueIfOutside(x, 0)\n            enqueueIfOutside(x, h - 1)\n        }\n        for y in 0..<h {\n            enqueueIfOutside(0, y)\n            enqueueIfOutside(w - 1, y)\n        }\n\n        while let (x, y) = queue.first {\n            queue.removeFirst()\n            enqueueIfOutside(x + 1, y)\n            enqueueIfOutside(x - 1, y)\n            enqueueIfOutside(x, y + 1)\n            enqueueIfOutside(x, y - 1)\n        }\n\n        // Any remaining transparent pixels not reachable from the border are internal holes (i.e. the eyes).\n        var internalHoles = 0\n        for y in 0..<h {\n            for x in 0..<w where isTransparent(x, y) && !visited[y][x] {\n                internalHoles += 1\n            }\n        }\n\n        #expect(internalHoles >= 16) // at least one 4×4 eye block, but typically two eyes => 32\n    }\n\n    @Test\n    func `icon renderer warp eyes cut out at expected centers`() {\n        // Regression: Warp eyes should be tilted in-place and remain centered on the face.\n        let image = IconRenderer.makeIcon(\n            primaryRemaining: 50,\n            weeklyRemaining: 50,\n            creditsRemaining: nil,\n            stale: false,\n            style: .warp)\n\n        let bitmapReps = image.representations.compactMap { $0 as? NSBitmapImageRep }\n        let rep = bitmapReps.first(where: { $0.pixelsWide == 36 && $0.pixelsHigh == 36 })\n        #expect(rep != nil)\n        guard let rep else { return }\n\n        func alphaAt(px x: Int, _ y: Int) -> CGFloat {\n            (rep.colorAt(x: x, y: y) ?? .clear).alphaComponent\n        }\n\n        func minAlphaNear(px cx: Int, _ cy: Int, radius: Int) -> CGFloat {\n            var minAlpha: CGFloat = 1.0\n            let x0 = max(0, cx - radius)\n            let x1 = min(rep.pixelsWide - 1, cx + radius)\n            let y0 = max(0, cy - radius)\n            let y1 = min(rep.pixelsHigh - 1, cy + radius)\n            for y in y0...y1 {\n                for x in x0...x1 {\n                    minAlpha = min(minAlpha, alphaAt(px: x, y))\n                }\n            }\n            return minAlpha\n        }\n\n        func minAlphaNearEitherOrigin(px cx: Int, _ cy: Int, radius: Int) -> CGFloat {\n            let flippedY = (rep.pixelsHigh - 1) - cy\n            return min(minAlphaNear(px: cx, cy, radius: radius), minAlphaNear(px: cx, flippedY, radius: radius))\n        }\n\n        // These are the center pixels for the two Warp eye cutouts in the top bar (36×36 canvas).\n        // If the eyes are rotated around the wrong origin, these points will not be fully punched out.\n        let leftEyeCenter = (x: 11, y: 25)\n        let rightEyeCenter = (x: 25, y: 25)\n\n        // The eye ellipse height is even (8 px), so the exact center can land between pixel rows.\n        // Assert via a small neighborhood search rather than a single pixel.\n        #expect(minAlphaNearEitherOrigin(px: leftEyeCenter.x, leftEyeCenter.y, radius: 2) < 0.05)\n        #expect(minAlphaNearEitherOrigin(px: rightEyeCenter.x, rightEyeCenter.y, radius: 2) < 0.05)\n\n        // Sanity: nearby top bar track area should remain visible (not everything is transparent).\n        let midAlpha = max(alphaAt(px: 18, 25), alphaAt(px: 18, (rep.pixelsHigh - 1) - 25))\n        #expect(midAlpha > 0.05)\n    }\n\n    @Test\n    func `account info parses auth token`() throws {\n        let tmp = try FileManager.default.url(\n            for: .itemReplacementDirectory,\n            in: .userDomainMask,\n            appropriateFor: URL(fileURLWithPath: NSTemporaryDirectory()),\n            create: true)\n        defer { try? FileManager.default.removeItem(at: tmp) }\n\n        let token = Self.fakeJWT(email: \"user@example.com\", plan: \"pro\")\n        let auth = [\"tokens\": [\"idToken\": token]]\n        let data = try JSONSerialization.data(withJSONObject: auth)\n        let authURL = tmp.appendingPathComponent(\"auth.json\")\n        try data.write(to: authURL)\n\n        let fetcher = UsageFetcher(environment: [\"CODEX_HOME\": tmp.path])\n        let account = fetcher.loadAccountInfo()\n        #expect(account.email == \"user@example.com\")\n        #expect(account.plan == \"pro\")\n    }\n\n    private static func fakeJWT(email: String, plan: String) -> String {\n        let header = (try? JSONSerialization.data(withJSONObject: [\"alg\": \"none\"])) ?? Data()\n        let payload = (try? JSONSerialization.data(withJSONObject: [\n            \"email\": email,\n            \"chatgpt_plan_type\": plan,\n        ])) ?? Data()\n        func b64(_ data: Data) -> String {\n            data.base64EncodedString()\n                .replacingOccurrences(of: \"=\", with: \"\")\n                .replacingOccurrences(of: \"+\", with: \"-\")\n                .replacingOccurrences(of: \"/\", with: \"_\")\n        }\n        return \"\\(b64(header)).\\(b64(payload)).\"\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/ConfigValidationTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n\nstruct ConfigValidationTests {\n    @Test\n    func `reports unsupported source`() {\n        var config = CodexBarConfig.makeDefault()\n        config.setProviderConfig(ProviderConfig(id: .codex, source: .api))\n        let issues = CodexBarConfigValidator.validate(config)\n        #expect(issues.contains(where: { $0.code == \"unsupported_source\" }))\n    }\n\n    @Test\n    func `reports missing API key when source API`() {\n        var config = CodexBarConfig.makeDefault()\n        config.setProviderConfig(ProviderConfig(id: .zai, source: .api, apiKey: nil))\n        let issues = CodexBarConfigValidator.validate(config)\n        #expect(issues.contains(where: { $0.code == \"api_key_missing\" }))\n    }\n\n    @Test\n    func `reports invalid region`() {\n        var config = CodexBarConfig.makeDefault()\n        config.setProviderConfig(ProviderConfig(id: .minimax, region: \"nowhere\"))\n        let issues = CodexBarConfigValidator.validate(config)\n        #expect(issues.contains(where: { $0.code == \"invalid_region\" }))\n    }\n\n    @Test\n    func `warns on unsupported token accounts`() {\n        let accounts = ProviderTokenAccountData(\n            version: 1,\n            accounts: [ProviderTokenAccount(id: UUID(), label: \"a\", token: \"t\", addedAt: 0, lastUsed: nil)],\n            activeIndex: 0)\n        var config = CodexBarConfig.makeDefault()\n        config.setProviderConfig(ProviderConfig(id: .gemini, tokenAccounts: accounts))\n        let issues = CodexBarConfigValidator.validate(config)\n        #expect(issues.contains(where: { $0.code == \"token_accounts_unused\" }))\n    }\n\n    @Test\n    func `allows ollama token accounts`() {\n        let accounts = ProviderTokenAccountData(\n            version: 1,\n            accounts: [ProviderTokenAccount(id: UUID(), label: \"a\", token: \"t\", addedAt: 0, lastUsed: nil)],\n            activeIndex: 0)\n        var config = CodexBarConfig.makeDefault()\n        config.setProviderConfig(ProviderConfig(id: .ollama, tokenAccounts: accounts))\n        let issues = CodexBarConfigValidator.validate(config)\n        #expect(!issues.contains(where: { $0.code == \"token_accounts_unused\" && $0.provider == .ollama }))\n    }\n\n    @Test\n    func `accepts kilo extras config field`() {\n        var config = CodexBarConfig.makeDefault()\n        config.setProviderConfig(ProviderConfig(id: .kilo, extrasEnabled: true))\n        let issues = CodexBarConfigValidator.validate(config)\n        #expect(!issues.contains(where: { $0.provider == .kilo && $0.field == \"extrasEnabled\" }))\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/CookieHeaderCacheTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\n@Suite(.serialized)\nstruct CookieHeaderCacheTests {\n    @Test\n    func `stores and loads entry`() {\n        KeychainCacheStore.setTestStoreForTesting(true)\n        defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n        let provider: UsageProvider = .codex\n        let storedAt = Date(timeIntervalSince1970: 0)\n        CookieHeaderCache.store(\n            provider: provider,\n            cookieHeader: \"auth=abc\",\n            sourceLabel: \"Chrome\",\n            now: storedAt)\n\n        let loaded = CookieHeaderCache.load(provider: provider)\n        defer { CookieHeaderCache.clear(provider: provider) }\n\n        #expect(loaded?.cookieHeader == \"auth=abc\")\n        #expect(loaded?.sourceLabel == \"Chrome\")\n        #expect(loaded?.storedAt == storedAt)\n    }\n\n    @Test\n    func `migrates legacy file to keychain`() {\n        KeychainCacheStore.setTestStoreForTesting(true)\n        defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n        let legacyBase = FileManager.default.temporaryDirectory\n            .appendingPathComponent(UUID().uuidString, isDirectory: true)\n        CookieHeaderCache.setLegacyBaseURLOverrideForTesting(legacyBase)\n        defer { CookieHeaderCache.setLegacyBaseURLOverrideForTesting(nil) }\n\n        let provider: UsageProvider = .codex\n        let storedAt = Date(timeIntervalSince1970: 0)\n        let entry = CookieHeaderCache.Entry(\n            cookieHeader: \"auth=legacy\",\n            storedAt: storedAt,\n            sourceLabel: \"Legacy\")\n        let legacyURL = legacyBase.appendingPathComponent(\"\\(provider.rawValue)-cookie.json\")\n\n        CookieHeaderCache.store(entry, to: legacyURL)\n        #expect(FileManager.default.fileExists(atPath: legacyURL.path) == true)\n\n        let loaded = CookieHeaderCache.load(provider: provider)\n        defer { CookieHeaderCache.clear(provider: provider) }\n\n        #expect(loaded?.cookieHeader == \"auth=legacy\")\n        #expect(loaded?.sourceLabel == \"Legacy\")\n        #expect(loaded?.storedAt == storedAt)\n        #expect(FileManager.default.fileExists(atPath: legacyURL.path) == false)\n\n        let loadedAgain = CookieHeaderCache.load(provider: provider)\n        #expect(loadedAgain?.cookieHeader == \"auth=legacy\")\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/CookieHeaderNormalizerTests.swift",
    "content": "import CodexBarCore\nimport Testing\n\nstruct CookieHeaderNormalizerTests {\n    @Test\n    func compactCurlShortFormWithoutWhitespaceStillParses() {\n        let normalized = CookieHeaderNormalizer.normalize(\"curl https://example.com -bfoo=bar\")\n\n        #expect(normalized == \"foo=bar\")\n        #expect(CookieHeaderNormalizer.pairs(from: \"curl https://example.com -bfoo=bar\").count == 1)\n        #expect(CookieHeaderNormalizer.pairs(from: \"curl https://example.com -bfoo=bar\").first?.name == \"foo\")\n        #expect(CookieHeaderNormalizer.pairs(from: \"curl https://example.com -bfoo=bar\").first?.value == \"bar\")\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/CopilotUsageModelsTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n\nstruct CopilotUsageModelsTests {\n    @Test\n    func `decodes quota snapshots payload`() throws {\n        let response = try Self.decodeFixture(\n            \"\"\"\n            {\n              \"copilot_plan\": \"free\",\n              \"assigned_date\": \"2025-01-01\",\n              \"quota_reset_date\": \"2025-02-01\",\n              \"quota_snapshots\": {\n                \"premium_interactions\": {\n                  \"entitlement\": 500,\n                  \"remaining\": 450,\n                  \"percent_remaining\": 90,\n                  \"quota_id\": \"premium_interactions\"\n                },\n                \"chat\": {\n                  \"entitlement\": 300,\n                  \"remaining\": 150,\n                  \"percent_remaining\": 50,\n                  \"quota_id\": \"chat\"\n                }\n              }\n            }\n            \"\"\")\n\n        #expect(response.copilotPlan == \"free\")\n        #expect(response.assignedDate == \"2025-01-01\")\n        #expect(response.quotaResetDate == \"2025-02-01\")\n        #expect(response.quotaSnapshots.premiumInteractions?.remaining == 450)\n        #expect(response.quotaSnapshots.chat?.remaining == 150)\n    }\n\n    @Test\n    func `decodes chat only quota snapshots payload`() throws {\n        let response = try Self.decodeFixture(\n            \"\"\"\n            {\n              \"copilot_plan\": \"free\",\n              \"quota_snapshots\": {\n                \"chat\": {\n                  \"entitlement\": 200,\n                  \"remaining\": 75,\n                  \"percent_remaining\": 37.5,\n                  \"quota_id\": \"chat\"\n                }\n              }\n            }\n            \"\"\")\n\n        #expect(response.quotaSnapshots.premiumInteractions == nil)\n        #expect(response.quotaSnapshots.chat?.quotaId == \"chat\")\n        #expect(response.quotaSnapshots.chat?.entitlement == 200)\n        #expect(response.quotaSnapshots.chat?.remaining == 75)\n    }\n\n    @Test\n    func `preserves missing date fields as nil`() throws {\n        let response = try Self.decodeFixture(\n            \"\"\"\n            {\n              \"copilot_plan\": \"free\",\n              \"quota_snapshots\": {\n                \"chat\": {\n                  \"entitlement\": 200,\n                  \"remaining\": 75,\n                  \"percent_remaining\": 37.5,\n                  \"quota_id\": \"chat\"\n                }\n              }\n            }\n            \"\"\")\n\n        #expect(response.assignedDate == nil)\n        #expect(response.quotaResetDate == nil)\n    }\n\n    @Test\n    func `preserves explicit empty date fields`() throws {\n        let response = try Self.decodeFixture(\n            \"\"\"\n            {\n              \"copilot_plan\": \"free\",\n              \"assigned_date\": \"\",\n              \"quota_reset_date\": \"\",\n              \"quota_snapshots\": {\n                \"chat\": {\n                  \"entitlement\": 200,\n                  \"remaining\": 75,\n                  \"percent_remaining\": 37.5,\n                  \"quota_id\": \"chat\"\n                }\n              }\n            }\n            \"\"\")\n\n        #expect(response.assignedDate?.isEmpty == true)\n        #expect(response.quotaResetDate?.isEmpty == true)\n    }\n\n    @Test\n    func `decodes monthly and limited quota payload`() throws {\n        let response = try Self.decodeFixture(\n            \"\"\"\n            {\n              \"copilot_plan\": \"free\",\n              \"monthly_quotas\": {\n                \"chat\": \"500\",\n                \"completions\": 300\n              },\n              \"limited_user_quotas\": {\n                \"chat\": 125,\n                \"completions\": \"75\"\n              }\n            }\n            \"\"\")\n\n        #expect(response.quotaSnapshots.premiumInteractions?.quotaId == \"completions\")\n        #expect(response.quotaSnapshots.premiumInteractions?.entitlement == 300)\n        #expect(response.quotaSnapshots.premiumInteractions?.remaining == 75)\n        #expect(response.quotaSnapshots.premiumInteractions?.percentRemaining == 25)\n\n        #expect(response.quotaSnapshots.chat?.quotaId == \"chat\")\n        #expect(response.quotaSnapshots.chat?.entitlement == 500)\n        #expect(response.quotaSnapshots.chat?.remaining == 125)\n        #expect(response.quotaSnapshots.chat?.percentRemaining == 25)\n    }\n\n    @Test\n    func `does not assume full quota when limited quotas are missing`() throws {\n        let response = try Self.decodeFixture(\n            \"\"\"\n            {\n              \"copilot_plan\": \"free\",\n              \"monthly_quotas\": {\n                \"chat\": 500,\n                \"completions\": 300\n              }\n            }\n            \"\"\")\n\n        #expect(response.quotaSnapshots.premiumInteractions == nil)\n        #expect(response.quotaSnapshots.chat == nil)\n    }\n\n    @Test\n    func `computes monthly fallback per quota only when limited value exists`() throws {\n        let response = try Self.decodeFixture(\n            \"\"\"\n            {\n              \"copilot_plan\": \"free\",\n              \"monthly_quotas\": {\n                \"chat\": 500,\n                \"completions\": 300\n              },\n              \"limited_user_quotas\": {\n                \"completions\": 60\n              }\n            }\n            \"\"\")\n\n        #expect(response.quotaSnapshots.premiumInteractions?.quotaId == \"completions\")\n        #expect(response.quotaSnapshots.premiumInteractions?.entitlement == 300)\n        #expect(response.quotaSnapshots.premiumInteractions?.remaining == 60)\n        #expect(response.quotaSnapshots.premiumInteractions?.percentRemaining == 20)\n        #expect(response.quotaSnapshots.chat == nil)\n    }\n\n    @Test\n    func `merges direct and monthly fallback lanes when direct is partial`() throws {\n        let response = try Self.decodeFixture(\n            \"\"\"\n            {\n              \"copilot_plan\": \"free\",\n              \"quota_snapshots\": {\n                \"chat\": {\n                  \"entitlement\": 200,\n                  \"remaining\": 75,\n                  \"percent_remaining\": 37.5,\n                  \"quota_id\": \"chat\"\n                }\n              },\n              \"monthly_quotas\": {\n                \"chat\": 500,\n                \"completions\": 300\n              },\n              \"limited_user_quotas\": {\n                \"chat\": 125,\n                \"completions\": 60\n              }\n            }\n            \"\"\")\n\n        #expect(response.quotaSnapshots.chat?.quotaId == \"chat\")\n        #expect(response.quotaSnapshots.chat?.entitlement == 200)\n        #expect(response.quotaSnapshots.chat?.remaining == 75)\n        #expect(response.quotaSnapshots.chat?.percentRemaining == 37.5)\n\n        #expect(response.quotaSnapshots.premiumInteractions?.quotaId == \"completions\")\n        #expect(response.quotaSnapshots.premiumInteractions?.entitlement == 300)\n        #expect(response.quotaSnapshots.premiumInteractions?.remaining == 60)\n        #expect(response.quotaSnapshots.premiumInteractions?.percentRemaining == 20)\n    }\n\n    @Test\n    func `decodes unknown quota snapshot keys using fallback`() throws {\n        let response = try Self.decodeFixture(\n            \"\"\"\n            {\n              \"copilot_plan\": \"free\",\n              \"quota_snapshots\": {\n                \"mystery_bucket\": {\n                  \"entitlement\": 100,\n                  \"remaining\": 40,\n                  \"percent_remaining\": 40,\n                  \"quota_id\": \"mystery_bucket\"\n                }\n              }\n            }\n            \"\"\")\n\n        #expect(response.quotaSnapshots.premiumInteractions == nil)\n        #expect(response.quotaSnapshots.chat?.quotaId == \"mystery_bucket\")\n        #expect(response.quotaSnapshots.chat?.entitlement == 100)\n        #expect(response.quotaSnapshots.chat?.remaining == 40)\n        #expect(response.quotaSnapshots.chat?.percentRemaining == 40)\n    }\n\n    @Test\n    func `ignores placeholder known snapshot when selecting unknown key fallback`() throws {\n        let response = try Self.decodeFixture(\n            \"\"\"\n            {\n              \"copilot_plan\": \"free\",\n              \"quota_snapshots\": {\n                \"premium_interactions\": {},\n                \"mystery_bucket\": {\n                  \"entitlement\": 100,\n                  \"remaining\": 40,\n                  \"percent_remaining\": 40,\n                  \"quota_id\": \"mystery_bucket\"\n                }\n              }\n            }\n            \"\"\")\n\n        #expect(response.quotaSnapshots.premiumInteractions == nil)\n        #expect(response.quotaSnapshots.chat?.quotaId == \"mystery_bucket\")\n        #expect(response.quotaSnapshots.chat?.hasPercentRemaining == true)\n    }\n\n    @Test\n    func `derives percent remaining when missing but entitlement exists`() throws {\n        let response = try Self.decodeFixture(\n            \"\"\"\n            {\n              \"copilot_plan\": \"free\",\n              \"quota_snapshots\": {\n                \"chat\": {\n                  \"entitlement\": 120,\n                  \"remaining\": 30,\n                  \"quota_id\": \"chat\"\n                }\n              }\n            }\n            \"\"\")\n\n        #expect(response.quotaSnapshots.chat?.hasPercentRemaining == true)\n        #expect(response.quotaSnapshots.chat?.percentRemaining == 25)\n    }\n\n    @Test\n    func `marks percent remaining as unavailable when underdetermined`() throws {\n        let response = try Self.decodeFixture(\n            \"\"\"\n            {\n              \"copilot_plan\": \"free\",\n              \"quota_snapshots\": {\n                \"chat\": {\n                  \"remaining\": 30,\n                  \"quota_id\": \"chat\"\n                }\n              }\n            }\n            \"\"\")\n\n        #expect(response.quotaSnapshots.chat?.hasPercentRemaining == false)\n        #expect(response.quotaSnapshots.chat?.percentRemaining == 0)\n    }\n\n    @Test\n    func `marks percent remaining as unavailable when remaining is missing`() throws {\n        let response = try Self.decodeFixture(\n            \"\"\"\n            {\n              \"copilot_plan\": \"free\",\n              \"quota_snapshots\": {\n                \"chat\": {\n                  \"entitlement\": 120,\n                  \"quota_id\": \"chat\"\n                }\n              }\n            }\n            \"\"\")\n\n        #expect(response.quotaSnapshots.chat?.hasPercentRemaining == false)\n        #expect(response.quotaSnapshots.chat?.percentRemaining == 0)\n    }\n\n    @Test\n    func `falls back to monthly when direct snapshot is missing remaining`() throws {\n        let response = try Self.decodeFixture(\n            \"\"\"\n            {\n              \"copilot_plan\": \"free\",\n              \"quota_snapshots\": {\n                \"chat\": {\n                  \"entitlement\": 120,\n                  \"quota_id\": \"chat\"\n                }\n              },\n              \"monthly_quotas\": {\n                \"chat\": 400\n              },\n              \"limited_user_quotas\": {\n                \"chat\": 100\n              }\n            }\n            \"\"\")\n\n        #expect(response.quotaSnapshots.premiumInteractions == nil)\n        #expect(response.quotaSnapshots.chat?.quotaId == \"chat\")\n        #expect(response.quotaSnapshots.chat?.entitlement == 400)\n        #expect(response.quotaSnapshots.chat?.remaining == 100)\n        #expect(response.quotaSnapshots.chat?.percentRemaining == 25)\n        #expect(response.quotaSnapshots.chat?.hasPercentRemaining == true)\n    }\n\n    @Test\n    func `falls back to monthly when direct snapshots lack computable percent`() throws {\n        let response = try Self.decodeFixture(\n            \"\"\"\n            {\n              \"copilot_plan\": \"free\",\n              \"quota_snapshots\": {\n                \"chat\": {\n                  \"remaining\": 30,\n                  \"quota_id\": \"chat\"\n                }\n              },\n              \"monthly_quotas\": {\n                \"chat\": 400\n              },\n              \"limited_user_quotas\": {\n                \"chat\": 100\n              }\n            }\n            \"\"\")\n\n        #expect(response.quotaSnapshots.premiumInteractions == nil)\n        #expect(response.quotaSnapshots.chat?.quotaId == \"chat\")\n        #expect(response.quotaSnapshots.chat?.entitlement == 400)\n        #expect(response.quotaSnapshots.chat?.remaining == 100)\n        #expect(response.quotaSnapshots.chat?.percentRemaining == 25)\n        #expect(response.quotaSnapshots.chat?.hasPercentRemaining == true)\n    }\n\n    @Test\n    func `skips monthly fallback when monthly denominator is zero`() throws {\n        let response = try Self.decodeFixture(\n            \"\"\"\n            {\n              \"copilot_plan\": \"free\",\n              \"monthly_quotas\": {\n                \"chat\": 0\n              },\n              \"limited_user_quotas\": {\n                \"chat\": 0\n              }\n            }\n            \"\"\")\n\n        #expect(response.quotaSnapshots.premiumInteractions == nil)\n        #expect(response.quotaSnapshots.chat == nil)\n    }\n\n    private static func decodeFixture(_ fixture: String) throws -> CopilotUsageResponse {\n        try JSONDecoder().decode(CopilotUsageResponse.self, from: Data(fixture.utf8))\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/CostUsageCacheTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\nstruct CostUsageCacheTests {\n    @Test\n    func `cache file URL uses codex specific artifact version`() {\n        let root = URL(fileURLWithPath: \"/tmp/codexbar-cost-cache\", isDirectory: true)\n\n        let codexURL = CostUsageCacheIO.cacheFileURL(provider: .codex, cacheRoot: root)\n        let claudeURL = CostUsageCacheIO.cacheFileURL(provider: .claude, cacheRoot: root)\n\n        #expect(codexURL.lastPathComponent == \"codex-v2.json\")\n        #expect(claudeURL.lastPathComponent == \"claude-v1.json\")\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/CostUsageDecodingTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\nstruct CostUsageDecodingTests {\n    @Test\n    func `decodes daily report type format`() throws {\n        let json = \"\"\"\n        {\n          \"type\": \"daily\",\n          \"data\": [\n            {\n              \"date\": \"2025-12-20\",\n              \"inputTokens\": 10,\n              \"cacheReadTokens\": 2,\n              \"cacheCreationTokens\": 3,\n              \"outputTokens\": 20,\n              \"totalTokens\": 30,\n              \"costUSD\": 0.12\n            }\n          ],\n          \"summary\": {\n            \"totalInputTokens\": 10,\n            \"totalOutputTokens\": 20,\n            \"cacheReadTokens\": 2,\n            \"cacheCreationTokens\": 3,\n            \"totalTokens\": 30,\n            \"totalCostUSD\": 0.12\n          }\n        }\n        \"\"\"\n\n        let report = try JSONDecoder().decode(CostUsageDailyReport.self, from: Data(json.utf8))\n        #expect(report.data.count == 1)\n        #expect(report.data[0].date == \"2025-12-20\")\n        #expect(report.data[0].totalTokens == 30)\n        #expect(report.data[0].cacheReadTokens == 2)\n        #expect(report.data[0].cacheCreationTokens == 3)\n        #expect(report.data[0].costUSD == 0.12)\n        #expect(report.summary?.totalCostUSD == 0.12)\n        #expect(report.summary?.cacheReadTokens == 2)\n        #expect(report.summary?.cacheCreationTokens == 3)\n    }\n\n    @Test\n    func `decodes daily report legacy format`() throws {\n        let json = \"\"\"\n        {\n          \"daily\": [\n            {\n              \"date\": \"2025-12-20\",\n              \"inputTokens\": 1,\n              \"cacheReadTokens\": 2,\n              \"cacheCreationTokens\": 3,\n              \"outputTokens\": 2,\n              \"totalTokens\": 3,\n              \"totalCost\": 0.01\n            }\n          ],\n          \"totals\": {\n            \"totalInputTokens\": 1,\n            \"totalOutputTokens\": 2,\n            \"cacheReadTokens\": 2,\n            \"cacheCreationTokens\": 3,\n            \"totalTokens\": 3,\n            \"totalCost\": 0.01\n          }\n        }\n        \"\"\"\n\n        let report = try JSONDecoder().decode(CostUsageDailyReport.self, from: Data(json.utf8))\n        #expect(report.data.count == 1)\n        #expect(report.summary?.totalTokens == 3)\n        #expect(report.summary?.totalCostUSD == 0.01)\n        #expect(report.data[0].cacheReadTokens == 2)\n        #expect(report.data[0].cacheCreationTokens == 3)\n        #expect(report.summary?.cacheReadTokens == 2)\n        #expect(report.summary?.cacheCreationTokens == 3)\n    }\n\n    @Test\n    func `decodes legacy cache token keys`() throws {\n        let json = \"\"\"\n        {\n          \"type\": \"daily\",\n          \"data\": [\n            {\n              \"date\": \"2025-12-20\",\n              \"cacheReadInputTokens\": 4,\n              \"cacheCreationInputTokens\": 5,\n              \"totalTokens\": 9\n            }\n          ],\n          \"summary\": {\n            \"totalCacheReadTokens\": 4,\n            \"totalCacheCreationTokens\": 5,\n            \"totalTokens\": 9\n          }\n        }\n        \"\"\"\n\n        let report = try JSONDecoder().decode(CostUsageDailyReport.self, from: Data(json.utf8))\n        #expect(report.data[0].cacheReadTokens == 4)\n        #expect(report.data[0].cacheCreationTokens == 5)\n        #expect(report.summary?.cacheReadTokens == 4)\n        #expect(report.summary?.cacheCreationTokens == 5)\n    }\n\n    @Test\n    func `decodes daily report legacy format with model map`() throws {\n        let json = \"\"\"\n        {\n          \"daily\": [\n            {\n              \"date\": \"Dec 20, 2025\",\n              \"inputTokens\": 10,\n              \"outputTokens\": 20,\n              \"totalTokens\": 30,\n              \"costUSD\": 0.12,\n              \"models\": {\n                \"gpt-5.2-codex\": {\n                  \"inputTokens\": 10,\n                  \"outputTokens\": 20,\n                  \"totalTokens\": 30,\n                  \"isFallback\": false\n                }\n              }\n            }\n          ],\n          \"totals\": {\n            \"totalTokens\": 30,\n            \"costUSD\": 0.12\n          }\n        }\n        \"\"\"\n\n        let report = try JSONDecoder().decode(CostUsageDailyReport.self, from: Data(json.utf8))\n        #expect(report.data.count == 1)\n        #expect(report.data[0].costUSD == 0.12)\n        #expect(report.data[0].modelsUsed == [\"gpt-5.2-codex\"])\n    }\n\n    @Test\n    func `decodes daily report legacy format with model map sorted`() throws {\n        let json = \"\"\"\n        {\n          \"daily\": [\n            {\n              \"date\": \"Dec 20, 2025\",\n              \"totalTokens\": 30,\n              \"costUSD\": 0.12,\n              \"models\": {\n                \"z-model\": { \"totalTokens\": 10 },\n                \"a-model\": { \"totalTokens\": 20 },\n                \"m-model\": { \"totalTokens\": 0 }\n              }\n            }\n          ]\n        }\n        \"\"\"\n\n        let report = try JSONDecoder().decode(CostUsageDailyReport.self, from: Data(json.utf8))\n        #expect(report.data[0].modelsUsed == [\"a-model\", \"m-model\", \"z-model\"])\n    }\n\n    @Test\n    func `decodes daily report legacy format with empty model map as nil`() throws {\n        let json = \"\"\"\n        {\n          \"daily\": [\n            {\n              \"date\": \"Dec 20, 2025\",\n              \"totalTokens\": 30,\n              \"costUSD\": 0.12,\n              \"models\": {}\n            }\n          ]\n        }\n        \"\"\"\n\n        let report = try JSONDecoder().decode(CostUsageDailyReport.self, from: Data(json.utf8))\n        #expect(report.data[0].modelsUsed == nil)\n    }\n\n    @Test\n    func `decodes daily report legacy format prefers models used list over models map`() throws {\n        let json = \"\"\"\n        {\n          \"daily\": [\n            {\n              \"date\": \"Dec 20, 2025\",\n              \"totalTokens\": 30,\n              \"costUSD\": 0.12,\n              \"modelsUsed\": [\"gpt-5.2-codex\"],\n              \"models\": {\n                \"ignored-model\": { \"totalTokens\": 30 }\n              }\n            }\n          ]\n        }\n        \"\"\"\n\n        let report = try JSONDecoder().decode(CostUsageDailyReport.self, from: Data(json.utf8))\n        #expect(report.data[0].modelsUsed == [\"gpt-5.2-codex\"])\n    }\n\n    @Test\n    func `decodes daily report legacy format with models list`() throws {\n        let json = \"\"\"\n        {\n          \"daily\": [\n            {\n              \"date\": \"Dec 20, 2025\",\n              \"totalTokens\": 30,\n              \"costUSD\": 0.12,\n              \"models\": [\"gpt-5.2-codex\", \"gpt-5.2-mini\"]\n            }\n          ]\n        }\n        \"\"\"\n\n        let report = try JSONDecoder().decode(CostUsageDailyReport.self, from: Data(json.utf8))\n        #expect(report.data[0].modelsUsed == [\"gpt-5.2-codex\", \"gpt-5.2-mini\"])\n    }\n\n    @Test\n    func `decodes model breakdown total tokens`() throws {\n        let json = \"\"\"\n        {\n          \"type\": \"daily\",\n          \"data\": [\n            {\n              \"date\": \"2025-12-20\",\n              \"totalTokens\": 30,\n              \"costUSD\": 0.12,\n              \"modelBreakdowns\": [\n                {\n                  \"modelName\": \"gpt-5.2-codex\",\n                  \"costUSD\": 0.12,\n                  \"totalTokens\": 30\n                }\n              ]\n            }\n          ]\n        }\n        \"\"\"\n\n        let report = try JSONDecoder().decode(CostUsageDailyReport.self, from: Data(json.utf8))\n        #expect(report.data[0].modelBreakdowns == [\n            CostUsageDailyReport.ModelBreakdown(modelName: \"gpt-5.2-codex\", costUSD: 0.12, totalTokens: 30),\n        ])\n    }\n\n    @Test\n    func `decodes daily report legacy format with invalid models field`() throws {\n        let json = \"\"\"\n        {\n          \"daily\": [\n            {\n              \"date\": \"Dec 20, 2025\",\n              \"totalTokens\": 30,\n              \"costUSD\": 0.12,\n              \"models\": \"gpt-5.2\"\n            }\n          ]\n        }\n        \"\"\"\n\n        let report = try JSONDecoder().decode(CostUsageDailyReport.self, from: Data(json.utf8))\n        #expect(report.data[0].modelsUsed == nil)\n    }\n\n    @Test\n    func `decodes monthly report legacy format`() throws {\n        let json = \"\"\"\n        {\n          \"monthly\": [\n            {\n              \"month\": \"Dec 2025\",\n              \"totalTokens\": 123,\n              \"costUSD\": 4.56\n            }\n          ],\n          \"totals\": {\n            \"totalTokens\": 123,\n            \"costUSD\": 4.56\n          }\n        }\n        \"\"\"\n\n        let report = try JSONDecoder().decode(CostUsageMonthlyReport.self, from: Data(json.utf8))\n        #expect(report.data.count == 1)\n        #expect(report.data[0].month == \"Dec 2025\")\n        #expect(report.data[0].costUSD == 4.56)\n        #expect(report.summary?.totalCostUSD == 4.56)\n    }\n\n    @Test\n    func `selects most recent session`() throws {\n        let json = \"\"\"\n        {\n          \"type\": \"session\",\n          \"data\": [\n            {\n              \"session\": \"A\",\n              \"totalTokens\": 100,\n              \"costUSD\": 0.50,\n              \"lastActivity\": \"2025-12-19\"\n            },\n            {\n              \"session\": \"B\",\n              \"totalTokens\": 50,\n              \"costUSD\": 0.20,\n              \"lastActivity\": \"2025-12-20T12:00:00Z\"\n            },\n            {\n              \"session\": \"C\",\n              \"totalTokens\": 200,\n              \"costUSD\": 0.10,\n              \"lastActivity\": \"2025-12-20T11:00:00Z\"\n            }\n          ],\n          \"summary\": {\n            \"totalCostUSD\": 0.80\n          }\n        }\n        \"\"\"\n\n        let report = try JSONDecoder().decode(CostUsageSessionReport.self, from: Data(json.utf8))\n        let selected = CostUsageFetcher.selectCurrentSession(from: report.data)\n        #expect(selected?.session == \"B\")\n    }\n\n    @Test\n    func `token snapshot selects most recent day`() throws {\n        let json = \"\"\"\n        {\n          \"type\": \"daily\",\n          \"data\": [\n            {\n              \"date\": \"Dec 20, 2025\",\n              \"totalTokens\": 30,\n              \"costUSD\": 1.23\n            },\n            {\n              \"date\": \"2025-12-21\",\n              \"totalTokens\": 10,\n              \"costUSD\": 4.56\n            }\n          ],\n          \"summary\": {\n            \"totalCostUSD\": 5.79\n          }\n        }\n        \"\"\"\n\n        let report = try JSONDecoder().decode(CostUsageDailyReport.self, from: Data(json.utf8))\n        let now = Date(timeIntervalSince1970: 1_766_275_200) // 2025-12-21\n        let snapshot = CostUsageFetcher.tokenSnapshot(from: report, now: now)\n        #expect(snapshot.sessionTokens == 10)\n        #expect(snapshot.sessionCostUSD == 4.56)\n        #expect(snapshot.last30DaysCostUSD == 5.79)\n        #expect(snapshot.daily.count == 2)\n        #expect(snapshot.updatedAt == now)\n    }\n\n    @Test\n    func `token snapshot uses summary total cost when available`() throws {\n        let json = \"\"\"\n        {\n          \"type\": \"daily\",\n          \"data\": [\n            { \"date\": \"2025-12-20\", \"costUSD\": 1.00 },\n            { \"date\": \"2025-12-21\", \"costUSD\": 2.00 }\n          ],\n          \"summary\": {\n            \"totalCostUSD\": 99.00\n          }\n        }\n        \"\"\"\n\n        let report = try JSONDecoder().decode(CostUsageDailyReport.self, from: Data(json.utf8))\n        let snapshot = CostUsageFetcher.tokenSnapshot(from: report, now: Date())\n        #expect(snapshot.last30DaysCostUSD == 99.00)\n    }\n\n    @Test\n    func `token snapshot falls back to summed entries when summary missing`() throws {\n        let json = \"\"\"\n        {\n          \"type\": \"daily\",\n          \"data\": [\n            { \"date\": \"2025-12-20\", \"costUSD\": 1.00 },\n            { \"date\": \"2025-12-21\", \"costUSD\": 2.00 }\n          ]\n        }\n        \"\"\"\n\n        let report = try JSONDecoder().decode(CostUsageDailyReport.self, from: Data(json.utf8))\n        let snapshot = CostUsageFetcher.tokenSnapshot(from: report, now: Date())\n        #expect(snapshot.last30DaysCostUSD == 3.00)\n    }\n\n    @Test\n    func `token snapshot returns nil total when no costs present`() throws {\n        let json = \"\"\"\n        {\n          \"type\": \"daily\",\n          \"data\": [\n            { \"date\": \"2025-12-20\", \"totalTokens\": 10 },\n            { \"date\": \"2025-12-21\", \"totalTokens\": 20 }\n          ]\n        }\n        \"\"\"\n\n        let report = try JSONDecoder().decode(CostUsageDailyReport.self, from: Data(json.utf8))\n        let snapshot = CostUsageFetcher.tokenSnapshot(from: report, now: Date())\n        #expect(snapshot.last30DaysCostUSD == nil)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/CostUsageJsonlPerformanceTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\n@Suite(.serialized)\nstruct CostUsageJsonlPerformanceTests {\n    @Test\n    func `scanner benchmark beats front buffer baseline`() throws {\n        let root = FileManager.default.temporaryDirectory.appendingPathComponent(\n            \"codexbar-cost-usage-bench-\\(UUID().uuidString)\",\n            isDirectory: true)\n        try FileManager.default.createDirectory(at: root, withIntermediateDirectories: true)\n        defer { try? FileManager.default.removeItem(at: root) }\n\n        let fileURL = root.appendingPathComponent(\"scanner-benchmark.jsonl\", isDirectory: false)\n        let lineCount = 20000\n        let line = #\"{\"type\":\"assistant\",\"message\":{\"usage\":{\"input_tokens\":1,\"output_tokens\":2}}}\"#\n        let fixture = makeBenchmarkFixture(line: line, lineCount: lineCount)\n        try fixture.write(to: fileURL)\n\n        let maxLineBytes = 8192\n        let prefixBytes = 8192\n\n        let currentSummary = try summarizeScan(\n            fileURL: fileURL,\n            maxLineBytes: maxLineBytes,\n            prefixBytes: prefixBytes,\n            scanner: CostUsageJsonl.scan)\n        let baselineSummary = try summarizeScan(\n            fileURL: fileURL,\n            maxLineBytes: maxLineBytes,\n            prefixBytes: prefixBytes,\n            scanner: scanWithFrontBufferBaseline)\n\n        #expect(currentSummary == baselineSummary)\n        #expect(currentSummary.lineCount == lineCount)\n        #expect(currentSummary.truncatedCount == 0)\n\n        // Warm up both code paths before timing.\n        _ = try summarizeScan(\n            fileURL: fileURL,\n            maxLineBytes: maxLineBytes,\n            prefixBytes: prefixBytes,\n            scanner: CostUsageJsonl.scan)\n        _ = try summarizeScan(\n            fileURL: fileURL,\n            maxLineBytes: maxLineBytes,\n            prefixBytes: prefixBytes,\n            scanner: scanWithFrontBufferBaseline)\n\n        let currentFastest = try fastestScanDurationNanoseconds(\n            runs: 3,\n            fileURL: fileURL,\n            maxLineBytes: maxLineBytes,\n            prefixBytes: prefixBytes,\n            scanner: CostUsageJsonl.scan)\n        let baselineFastest = try fastestScanDurationNanoseconds(\n            runs: 3,\n            fileURL: fileURL,\n            maxLineBytes: maxLineBytes,\n            prefixBytes: prefixBytes,\n            scanner: scanWithFrontBufferBaseline)\n\n        let speedup = Double(baselineFastest) / Double(currentFastest)\n        #expect(speedup >= 5.0)\n    }\n}\n\nprivate struct JsonlScanSummary: Equatable {\n    let lineCount: Int\n    let truncatedCount: Int\n    let payloadByteCount: Int\n    let endOffset: Int64\n}\n\nprivate typealias JsonlScanner = (\n    _ fileURL: URL,\n    _ offset: Int64,\n    _ maxLineBytes: Int,\n    _ prefixBytes: Int,\n    _ onLine: (CostUsageJsonl.Line) -> Void) throws -> Int64\n\nprivate func makeBenchmarkFixture(line: String, lineCount: Int) -> Data {\n    let lineBytes = Data(line.utf8)\n    var data = Data()\n    data.reserveCapacity((lineBytes.count + 1) * lineCount)\n    for _ in 0..<lineCount {\n        data.append(lineBytes)\n        data.append(0x0A)\n    }\n    return data\n}\n\nprivate func summarizeScan(\n    fileURL: URL,\n    maxLineBytes: Int,\n    prefixBytes: Int,\n    scanner: JsonlScanner) throws -> JsonlScanSummary\n{\n    var lineCount = 0\n    var truncatedCount = 0\n    var payloadByteCount = 0\n\n    let endOffset = try scanner(fileURL, 0, maxLineBytes, prefixBytes) { line in\n        lineCount += 1\n        payloadByteCount += line.bytes.count\n        if line.wasTruncated {\n            truncatedCount += 1\n        }\n    }\n\n    return JsonlScanSummary(\n        lineCount: lineCount,\n        truncatedCount: truncatedCount,\n        payloadByteCount: payloadByteCount,\n        endOffset: endOffset)\n}\n\nprivate func fastestScanDurationNanoseconds(\n    runs: Int,\n    fileURL: URL,\n    maxLineBytes: Int,\n    prefixBytes: Int,\n    scanner: JsonlScanner) throws -> UInt64\n{\n    var fastest = UInt64.max\n    for _ in 0..<runs {\n        let startedAt = DispatchTime.now().uptimeNanoseconds\n        _ = try summarizeScan(\n            fileURL: fileURL,\n            maxLineBytes: maxLineBytes,\n            prefixBytes: prefixBytes,\n            scanner: scanner)\n        let elapsed = DispatchTime.now().uptimeNanoseconds - startedAt\n        fastest = min(fastest, elapsed)\n    }\n    return fastest\n}\n\n@discardableResult\nprivate func scanWithFrontBufferBaseline(\n    fileURL: URL,\n    offset: Int64 = 0,\n    maxLineBytes: Int,\n    prefixBytes: Int,\n    onLine: (CostUsageJsonl.Line) -> Void) throws\n    -> Int64\n{\n    let handle = try FileHandle(forReadingFrom: fileURL)\n    defer { try? handle.close() }\n\n    let startOffset = max(0, offset)\n    if startOffset > 0 {\n        try handle.seek(toOffset: UInt64(startOffset))\n    }\n\n    var buffer = Data()\n    buffer.reserveCapacity(64 * 1024)\n\n    var current = Data()\n    current.reserveCapacity(4 * 1024)\n    var lineBytes = 0\n    var truncated = false\n    var bytesRead: Int64 = 0\n\n    func flushLine() {\n        guard lineBytes > 0 else { return }\n        onLine(.init(bytes: current, wasTruncated: truncated))\n        current.removeAll(keepingCapacity: true)\n        lineBytes = 0\n        truncated = false\n    }\n\n    while true {\n        let chunk = try handle.read(upToCount: 256 * 1024) ?? Data()\n        if chunk.isEmpty {\n            flushLine()\n            break\n        }\n\n        bytesRead += Int64(chunk.count)\n        buffer.append(chunk)\n\n        while true {\n            guard let nl = buffer.firstIndex(of: 0x0A) else { break }\n            let linePart = buffer[..<nl]\n            buffer.removeSubrange(...nl)\n\n            lineBytes += linePart.count\n            if !truncated {\n                if lineBytes > maxLineBytes || lineBytes > prefixBytes {\n                    truncated = true\n                    current.removeAll(keepingCapacity: true)\n                } else {\n                    current.append(contentsOf: linePart)\n                }\n            }\n\n            flushLine()\n        }\n    }\n\n    return startOffset + bytesRead\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/CostUsageJsonlScannerTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\nstruct CostUsageJsonlScannerTests {\n    @Test\n    func `jsonl scanner handles lines across read chunks`() throws {\n        let root = try self.makeTemporaryRoot()\n        defer { try? FileManager.default.removeItem(at: root) }\n\n        let fileURL = root.appendingPathComponent(\"large-lines.jsonl\", isDirectory: false)\n        let largeLine = String(repeating: \"x\", count: 300_000)\n        let contents = \"\\(largeLine)\\nsmall\\n\"\n        try contents.write(to: fileURL, atomically: true, encoding: .utf8)\n\n        var scanned: [(count: Int, truncated: Bool)] = []\n        let endOffset = try CostUsageJsonl.scan(\n            fileURL: fileURL,\n            maxLineBytes: 400_000,\n            prefixBytes: 400_000)\n        { line in\n            scanned.append((line.bytes.count, line.wasTruncated))\n        }\n\n        #expect(endOffset == Int64(Data(contents.utf8).count))\n        #expect(scanned.count == 2)\n        #expect(scanned[0].count == 300_000)\n        #expect(scanned[0].truncated == false)\n        #expect(scanned[1].count == 5)\n        #expect(scanned[1].truncated == false)\n    }\n\n    @Test\n    func `jsonl scanner marks prefix limited lines as truncated`() throws {\n        let root = try self.makeTemporaryRoot()\n        defer { try? FileManager.default.removeItem(at: root) }\n\n        let fileURL = root.appendingPathComponent(\"truncated-lines.jsonl\", isDirectory: false)\n        let shortLine = \"ok\"\n        let longLine = String(repeating: \"a\", count: 2000)\n        let contents = \"\\(shortLine)\\n\\(longLine)\\n\"\n        try contents.write(to: fileURL, atomically: true, encoding: .utf8)\n\n        var scanned: [CostUsageJsonl.Line] = []\n        _ = try CostUsageJsonl.scan(\n            fileURL: fileURL,\n            maxLineBytes: 10000,\n            prefixBytes: 64)\n        { line in\n            scanned.append(line)\n        }\n\n        #expect(scanned.count == 2)\n        #expect(String(data: scanned[0].bytes, encoding: .utf8) == \"ok\")\n        #expect(scanned[0].wasTruncated == false)\n        #expect(scanned[1].bytes.isEmpty)\n        #expect(scanned[1].wasTruncated == true)\n    }\n\n    private func makeTemporaryRoot() throws -> URL {\n        let root = FileManager.default.temporaryDirectory.appendingPathComponent(\n            \"codexbar-cost-usage-jsonl-\\(UUID().uuidString)\",\n            isDirectory: true)\n        try FileManager.default.createDirectory(at: root, withIntermediateDirectories: true)\n        return root\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/CostUsagePricingTests.swift",
    "content": "import Testing\n@testable import CodexBarCore\n\nstruct CostUsagePricingTests {\n    @Test\n    func `normalizes codex model variants exactly`() {\n        #expect(CostUsagePricing.normalizeCodexModel(\"openai/gpt-5-codex\") == \"gpt-5-codex\")\n        #expect(CostUsagePricing.normalizeCodexModel(\"gpt-5.2-codex\") == \"gpt-5.2-codex\")\n        #expect(CostUsagePricing.normalizeCodexModel(\"gpt-5.1-codex-max\") == \"gpt-5.1-codex-max\")\n        #expect(CostUsagePricing.normalizeCodexModel(\"gpt-5.4-pro-2026-03-05\") == \"gpt-5.4-pro\")\n        #expect(CostUsagePricing.normalizeCodexModel(\"gpt-5.4-mini-2026-03-17\") == \"gpt-5.4-mini\")\n        #expect(CostUsagePricing.normalizeCodexModel(\"gpt-5.4-nano-2026-03-17\") == \"gpt-5.4-nano\")\n        #expect(CostUsagePricing.normalizeCodexModel(\"gpt-5.3-codex-2026-03-05\") == \"gpt-5.3-codex\")\n        #expect(CostUsagePricing.normalizeCodexModel(\"gpt-5.3-codex-spark\") == \"gpt-5.3-codex-spark\")\n    }\n\n    @Test\n    func `codex cost supports gpt51 codex max`() {\n        let cost = CostUsagePricing.codexCostUSD(\n            model: \"gpt-5.1-codex-max\",\n            inputTokens: 100,\n            cachedInputTokens: 10,\n            outputTokens: 5)\n        #expect(cost != nil)\n    }\n\n    @Test\n    func `codex cost supports gpt53 codex`() {\n        let cost = CostUsagePricing.codexCostUSD(\n            model: \"gpt-5.3-codex\",\n            inputTokens: 100,\n            cachedInputTokens: 10,\n            outputTokens: 5)\n        #expect(cost != nil)\n    }\n\n    @Test\n    func `codex cost supports gpt54 mini and nano`() {\n        let mini = CostUsagePricing.codexCostUSD(\n            model: \"gpt-5.4-mini-2026-03-17\",\n            inputTokens: 100,\n            cachedInputTokens: 10,\n            outputTokens: 5)\n        let nano = CostUsagePricing.codexCostUSD(\n            model: \"gpt-5.4-nano\",\n            inputTokens: 100,\n            cachedInputTokens: 10,\n            outputTokens: 5)\n\n        #expect(mini != nil)\n        #expect(nano != nil)\n    }\n\n    @Test\n    func `codex cost returns zero for research preview model`() {\n        let cost = CostUsagePricing.codexCostUSD(\n            model: \"gpt-5.3-codex-spark\",\n            inputTokens: 100,\n            cachedInputTokens: 10,\n            outputTokens: 5)\n        #expect(cost == 0)\n        #expect(CostUsagePricing.codexDisplayLabel(model: \"gpt-5.3-codex-spark\") == \"Research Preview\")\n        #expect(CostUsagePricing.codexDisplayLabel(model: \"gpt-5.2-codex\") == nil)\n    }\n\n    @Test\n    func `normalizes claude opus41 dated variants`() {\n        #expect(CostUsagePricing.normalizeClaudeModel(\"claude-opus-4-1-20250805\") == \"claude-opus-4-1\")\n    }\n\n    @Test\n    func `claude cost supports opus41 dated variant`() {\n        let cost = CostUsagePricing.claudeCostUSD(\n            model: \"claude-opus-4-1-20250805\",\n            inputTokens: 10,\n            cacheReadInputTokens: 0,\n            cacheCreationInputTokens: 0,\n            outputTokens: 5)\n        #expect(cost != nil)\n    }\n\n    @Test\n    func `claude cost supports opus46 dated variant`() {\n        let cost = CostUsagePricing.claudeCostUSD(\n            model: \"claude-opus-4-6-20260205\",\n            inputTokens: 10,\n            cacheReadInputTokens: 0,\n            cacheCreationInputTokens: 0,\n            outputTokens: 5)\n        #expect(cost != nil)\n    }\n\n    @Test\n    func `claude cost returns nil for unknown models`() {\n        let cost = CostUsagePricing.claudeCostUSD(\n            model: \"glm-4.6\",\n            inputTokens: 100,\n            cacheReadInputTokens: 500,\n            cacheCreationInputTokens: 0,\n            outputTokens: 40)\n        #expect(cost == nil)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/CostUsageScannerBreakdownTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\nstruct CostUsageScannerBreakdownTests {\n    @Test\n    func `codex daily report parses token counts and caches`() throws {\n        let env = try CostUsageTestEnvironment()\n        defer { env.cleanup() }\n\n        let day = try env.makeLocalNoon(year: 2025, month: 12, day: 20)\n        let iso0 = env.isoString(for: day)\n        let iso1 = env.isoString(for: day.addingTimeInterval(1))\n        let iso2 = env.isoString(for: day.addingTimeInterval(2))\n\n        let model = \"openai/gpt-5.2-codex\"\n        let turnContext: [String: Any] = [\n            \"type\": \"turn_context\",\n            \"timestamp\": iso0,\n            \"payload\": [\n                \"model\": model,\n            ],\n        ]\n        let firstTokenCount: [String: Any] = [\n            \"type\": \"event_msg\",\n            \"timestamp\": iso1,\n            \"payload\": [\n                \"type\": \"token_count\",\n                \"info\": [\n                    \"total_token_usage\": [\n                        \"input_tokens\": 100,\n                        \"cached_input_tokens\": 20,\n                        \"output_tokens\": 10,\n                    ],\n                    \"model\": model,\n                ],\n            ],\n        ]\n\n        let fileURL = try env.writeCodexSessionFile(\n            day: day,\n            filename: \"session.jsonl\",\n            contents: env.jsonl([turnContext, firstTokenCount]))\n\n        var options = CostUsageScanner.Options(\n            codexSessionsRoot: env.codexSessionsRoot,\n            claudeProjectsRoots: nil,\n            cacheRoot: env.cacheRoot)\n        options.refreshMinIntervalSeconds = 0\n\n        let first = CostUsageScanner.loadDailyReport(\n            provider: .codex,\n            since: day,\n            until: day,\n            now: day,\n            options: options)\n        #expect(first.data.count == 1)\n        #expect(first.data[0].modelsUsed == [\"gpt-5.2-codex\"])\n        #expect(first.data[0].modelBreakdowns == [\n            CostUsageDailyReport.ModelBreakdown(\n                modelName: \"gpt-5.2-codex\",\n                costUSD: first.data[0].costUSD,\n                totalTokens: 110),\n        ])\n        #expect(first.data[0].totalTokens == 110)\n        #expect((first.data[0].costUSD ?? 0) > 0)\n\n        let secondTokenCount: [String: Any] = [\n            \"type\": \"event_msg\",\n            \"timestamp\": iso2,\n            \"payload\": [\n                \"type\": \"token_count\",\n                \"info\": [\n                    \"total_token_usage\": [\n                        \"input_tokens\": 160,\n                        \"cached_input_tokens\": 40,\n                        \"output_tokens\": 16,\n                    ],\n                    \"model\": model,\n                ],\n            ],\n        ]\n        try env.jsonl([turnContext, firstTokenCount, secondTokenCount])\n            .write(to: fileURL, atomically: true, encoding: .utf8)\n\n        let second = CostUsageScanner.loadDailyReport(\n            provider: .codex,\n            since: day,\n            until: day,\n            now: day,\n            options: options)\n        #expect(second.data.count == 1)\n        #expect(second.data[0].modelsUsed == [\"gpt-5.2-codex\"])\n        #expect(second.data[0].totalTokens == 176)\n        #expect((second.data[0].costUSD ?? 0) > (first.data[0].costUSD ?? 0))\n    }\n\n    @Test\n    func `codex daily report includes archived sessions and dedupes`() throws {\n        let env = try CostUsageTestEnvironment()\n        defer { env.cleanup() }\n\n        let day = try env.makeLocalNoon(year: 2025, month: 12, day: 22)\n        let iso0 = env.isoString(for: day)\n        let iso1 = env.isoString(for: day.addingTimeInterval(1))\n\n        let model = \"openai/gpt-5.2-codex\"\n        let sessionMeta: [String: Any] = [\n            \"type\": \"session_meta\",\n            \"payload\": [\n                \"session_id\": \"sess-archived-1\",\n            ],\n        ]\n        let turnContext: [String: Any] = [\n            \"type\": \"turn_context\",\n            \"timestamp\": iso0,\n            \"payload\": [\n                \"model\": model,\n            ],\n        ]\n        let tokenCount: [String: Any] = [\n            \"type\": \"event_msg\",\n            \"timestamp\": iso1,\n            \"payload\": [\n                \"type\": \"token_count\",\n                \"info\": [\n                    \"total_token_usage\": [\n                        \"input_tokens\": 100,\n                        \"cached_input_tokens\": 20,\n                        \"output_tokens\": 10,\n                    ],\n                    \"model\": model,\n                ],\n            ],\n        ]\n\n        let comps = Calendar.current.dateComponents([.year, .month, .day], from: day)\n        let dayKey = String(format: \"%04d-%02d-%02d\", comps.year ?? 1970, comps.month ?? 1, comps.day ?? 1)\n        let archivedName = \"rollout-\\(dayKey)T12-00-00-archived.jsonl\"\n        let contents = try env.jsonl([sessionMeta, turnContext, tokenCount])\n        _ = try env.writeCodexArchivedSessionFile(filename: archivedName, contents: contents)\n\n        var options = CostUsageScanner.Options(\n            codexSessionsRoot: env.codexSessionsRoot,\n            claudeProjectsRoots: nil,\n            cacheRoot: env.cacheRoot)\n        options.refreshMinIntervalSeconds = 0\n\n        let first = CostUsageScanner.loadDailyReport(\n            provider: .codex,\n            since: day,\n            until: day,\n            now: day,\n            options: options)\n        #expect(first.data.count == 1)\n        #expect(first.data[0].totalTokens == 110)\n\n        _ = try env.writeCodexSessionFile(day: day, filename: \"session.jsonl\", contents: contents)\n        let second = CostUsageScanner.loadDailyReport(\n            provider: .codex,\n            since: day,\n            until: day,\n            now: day,\n            options: options)\n        #expect(second.data.count == 1)\n        #expect(second.data[0].totalTokens == 110)\n    }\n\n    @Test\n    func `claude daily report parses usage and caches`() throws {\n        let env = try CostUsageTestEnvironment()\n        defer { env.cleanup() }\n\n        let day = try env.makeLocalNoon(year: 2025, month: 12, day: 20)\n        let iso0 = env.isoString(for: day)\n\n        let assistant: [String: Any] = [\n            \"type\": \"assistant\",\n            \"timestamp\": iso0,\n            \"message\": [\n                \"model\": \"claude-sonnet-4-20250514\",\n                \"usage\": [\n                    \"input_tokens\": 200,\n                    \"cache_creation_input_tokens\": 50,\n                    \"cache_read_input_tokens\": 25,\n                    \"output_tokens\": 80,\n                ],\n            ],\n        ]\n        _ = try env.writeClaudeProjectFile(\n            relativePath: \"project-a/session-a.jsonl\",\n            contents: env.jsonl([assistant]))\n\n        var options = CostUsageScanner.Options(\n            codexSessionsRoot: nil,\n            claudeProjectsRoots: [env.claudeProjectsRoot],\n            cacheRoot: env.cacheRoot)\n        options.refreshMinIntervalSeconds = 0\n\n        let report = CostUsageScanner.loadDailyReport(\n            provider: .claude,\n            since: day,\n            until: day,\n            now: day,\n            options: options)\n        #expect(report.data.count == 1)\n        #expect(report.data[0].modelsUsed == [\"claude-sonnet-4-20250514\"])\n        #expect(report.data[0].inputTokens == 200)\n        #expect(report.data[0].cacheCreationTokens == 50)\n        #expect(report.data[0].cacheReadTokens == 25)\n        #expect(report.data[0].outputTokens == 80)\n        #expect(report.data[0].totalTokens == 355)\n        #expect(report.data[0].modelBreakdowns == [\n            CostUsageDailyReport.ModelBreakdown(\n                modelName: \"claude-sonnet-4-20250514\",\n                costUSD: report.data[0].costUSD,\n                totalTokens: 355),\n        ])\n        #expect((report.data[0].costUSD ?? 0) > 0)\n    }\n\n    @Test\n    func `codex daily report preserves full sorted model breakdowns`() throws {\n        let env = try CostUsageTestEnvironment()\n        defer { env.cleanup() }\n\n        let day = try env.makeLocalNoon(year: 2025, month: 12, day: 23)\n        let iso0 = env.isoString(for: day)\n        let iso1 = env.isoString(for: day.addingTimeInterval(1))\n        let iso2 = env.isoString(for: day.addingTimeInterval(2))\n        let iso3 = env.isoString(for: day.addingTimeInterval(3))\n        let iso4 = env.isoString(for: day.addingTimeInterval(4))\n        let iso5 = env.isoString(for: day.addingTimeInterval(5))\n        let iso6 = env.isoString(for: day.addingTimeInterval(6))\n        let iso7 = env.isoString(for: day.addingTimeInterval(7))\n\n        let events: [[String: Any]] = [\n            [\n                \"type\": \"turn_context\",\n                \"timestamp\": iso0,\n                \"payload\": [\"model\": \"openai/gpt-5.2-pro\"],\n            ],\n            [\n                \"type\": \"event_msg\",\n                \"timestamp\": iso1,\n                \"payload\": [\n                    \"type\": \"token_count\",\n                    \"info\": [\n                        \"last_token_usage\": [\n                            \"input_tokens\": 100,\n                            \"cached_input_tokens\": 0,\n                            \"output_tokens\": 10,\n                        ],\n                    ],\n                ],\n            ],\n            [\n                \"type\": \"turn_context\",\n                \"timestamp\": iso2,\n                \"payload\": [\"model\": \"openai/gpt-5.3-codex\"],\n            ],\n            [\n                \"type\": \"event_msg\",\n                \"timestamp\": iso3,\n                \"payload\": [\n                    \"type\": \"token_count\",\n                    \"info\": [\n                        \"last_token_usage\": [\n                            \"input_tokens\": 30,\n                            \"cached_input_tokens\": 0,\n                            \"output_tokens\": 10,\n                        ],\n                    ],\n                ],\n            ],\n            [\n                \"type\": \"turn_context\",\n                \"timestamp\": iso4,\n                \"payload\": [\"model\": \"openai/gpt-5.2-codex\"],\n            ],\n            [\n                \"type\": \"event_msg\",\n                \"timestamp\": iso5,\n                \"payload\": [\n                    \"type\": \"token_count\",\n                    \"info\": [\n                        \"last_token_usage\": [\n                            \"input_tokens\": 20,\n                            \"cached_input_tokens\": 0,\n                            \"output_tokens\": 10,\n                        ],\n                    ],\n                ],\n            ],\n            [\n                \"type\": \"turn_context\",\n                \"timestamp\": iso6,\n                \"payload\": [\"model\": \"openai/gpt-5.3-codex-spark\"],\n            ],\n            [\n                \"type\": \"event_msg\",\n                \"timestamp\": iso7,\n                \"payload\": [\n                    \"type\": \"token_count\",\n                    \"info\": [\n                        \"last_token_usage\": [\n                            \"input_tokens\": 10,\n                            \"cached_input_tokens\": 0,\n                            \"output_tokens\": 5,\n                        ],\n                    ],\n                ],\n            ],\n        ]\n\n        _ = try env.writeCodexSessionFile(\n            day: day,\n            filename: \"session.jsonl\",\n            contents: env.jsonl(events))\n\n        var options = CostUsageScanner.Options(\n            codexSessionsRoot: env.codexSessionsRoot,\n            claudeProjectsRoots: nil,\n            cacheRoot: env.cacheRoot)\n        options.refreshMinIntervalSeconds = 0\n\n        let report = CostUsageScanner.loadDailyReport(\n            provider: .codex,\n            since: day,\n            until: day,\n            now: day,\n            options: options)\n\n        #expect(report.data.count == 1)\n        #expect(report.data[0].modelBreakdowns?.map(\\.modelName) == [\n            \"gpt-5.2-pro\",\n            \"gpt-5.3-codex\",\n            \"gpt-5.2-codex\",\n            \"gpt-5.3-codex-spark\",\n        ])\n        #expect(report.data[0].modelBreakdowns?.map(\\.totalTokens) == [110, 40, 30, 15])\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/CostUsageScannerTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\nstruct CostUsageScannerTests {\n    @Test\n    func `vertex daily report filters claude logs`() throws {\n        let env = try CostUsageTestEnvironment()\n        defer { env.cleanup() }\n\n        let day = try env.makeLocalNoon(year: 2025, month: 12, day: 20)\n        let iso0 = env.isoString(for: day)\n\n        let vertexEntry: [String: Any] = [\n            \"type\": \"assistant\",\n            \"timestamp\": iso0,\n            \"metadata\": [\n                \"provider\": \"vertexai\",\n                \"projectId\": \"vertex-project\",\n                \"location\": \"us-central1\",\n            ],\n            \"message\": [\n                \"model\": \"claude-sonnet-4-20250514\",\n                \"usage\": [\n                    \"input_tokens\": 10,\n                    \"cache_creation_input_tokens\": 0,\n                    \"cache_read_input_tokens\": 0,\n                    \"output_tokens\": 5,\n                ],\n            ],\n        ]\n        let claudeEntry: [String: Any] = [\n            \"type\": \"assistant\",\n            \"timestamp\": iso0,\n            \"metadata\": [\n                \"provider\": \"anthropic\",\n            ],\n            \"message\": [\n                \"model\": \"claude-sonnet-4-20250514\",\n                \"usage\": [\n                    \"input_tokens\": 200,\n                    \"cache_creation_input_tokens\": 0,\n                    \"cache_read_input_tokens\": 0,\n                    \"output_tokens\": 100,\n                ],\n            ],\n        ]\n\n        _ = try env.writeClaudeProjectFile(\n            relativePath: \"project-a/session-a.jsonl\",\n            contents: env.jsonl([vertexEntry, claudeEntry]))\n\n        var options = CostUsageScanner.Options(\n            codexSessionsRoot: nil,\n            claudeProjectsRoots: [env.claudeProjectsRoot],\n            cacheRoot: env.cacheRoot)\n        options.refreshMinIntervalSeconds = 0\n\n        let report = CostUsageScanner.loadDailyReport(\n            provider: .vertexai,\n            since: day,\n            until: day,\n            now: day,\n            options: options)\n\n        #expect(report.data.count == 1)\n        #expect(report.data[0].inputTokens == 10)\n        #expect(report.data[0].outputTokens == 5)\n        #expect(report.data[0].totalTokens == 15)\n    }\n\n    @Test\n    func `vertex daily report detects by vrtx id prefix`() throws {\n        let env = try CostUsageTestEnvironment()\n        defer { env.cleanup() }\n\n        let day = try env.makeLocalNoon(year: 2025, month: 12, day: 20)\n        let iso0 = env.isoString(for: day)\n        let iso1 = env.isoString(for: day.addingTimeInterval(1))\n\n        // Vertex AI entries have \"_vrtx_\" in message.id and requestId\n        let vertexEntry: [String: Any] = [\n            \"type\": \"assistant\",\n            \"timestamp\": iso0,\n            \"requestId\": \"req_vrtx_011CWjK86SWeFuXqZKUtgB1H\",\n            \"message\": [\n                \"id\": \"msg_vrtx_0154LUXjFVzQGUca3yK2RUeo\",\n                \"model\": \"claude-opus-4-5-20251101\",\n                \"usage\": [\n                    \"input_tokens\": 100,\n                    \"cache_creation_input_tokens\": 0,\n                    \"cache_read_input_tokens\": 0,\n                    \"output_tokens\": 50,\n                ],\n            ],\n        ]\n        // Anthropic API entries have regular IDs without \"_vrtx_\"\n        let claudeEntry: [String: Any] = [\n            \"type\": \"assistant\",\n            \"timestamp\": iso1,\n            \"requestId\": \"req_011CW7BFSFkbK9qJrV8kiptH\",\n            \"message\": [\n                \"id\": \"msg_0152zX6DsQYcwH1qiXi4B3y2\",\n                \"model\": \"claude-opus-4-5-20251101\",\n                \"usage\": [\n                    \"input_tokens\": 200,\n                    \"cache_creation_input_tokens\": 0,\n                    \"cache_read_input_tokens\": 0,\n                    \"output_tokens\": 100,\n                ],\n            ],\n        ]\n\n        _ = try env.writeClaudeProjectFile(\n            relativePath: \"project-a/session-a.jsonl\",\n            contents: env.jsonl([vertexEntry, claudeEntry]))\n\n        var options = CostUsageScanner.Options(\n            codexSessionsRoot: nil,\n            claudeProjectsRoots: [env.claudeProjectsRoot],\n            cacheRoot: env.cacheRoot)\n        options.refreshMinIntervalSeconds = 0\n\n        // Vertex AI report should only include entries with _vrtx_ prefix\n        let vertexReport = CostUsageScanner.loadDailyReport(\n            provider: .vertexai,\n            since: day,\n            until: day,\n            now: day,\n            options: options)\n\n        #expect(vertexReport.data.count == 1)\n        #expect(vertexReport.data[0].inputTokens == 100)\n        #expect(vertexReport.data[0].outputTokens == 50)\n        #expect(vertexReport.data[0].totalTokens == 150)\n\n        // Claude report with excludeVertexAI should only include non-vrtx entries\n        var claudeOptions = options\n        claudeOptions.claudeLogProviderFilter = .excludeVertexAI\n        let claudeReport = CostUsageScanner.loadDailyReport(\n            provider: .claude,\n            since: day,\n            until: day,\n            now: day,\n            options: claudeOptions)\n\n        #expect(claudeReport.data.count == 1)\n        #expect(claudeReport.data[0].inputTokens == 200)\n        #expect(claudeReport.data[0].outputTokens == 100)\n        #expect(claudeReport.data[0].totalTokens == 300)\n    }\n\n    @Test\n    func `claude parses large lines with usage at tail`() throws {\n        let env = try CostUsageTestEnvironment()\n        defer { env.cleanup() }\n\n        let day = try env.makeLocalNoon(year: 2025, month: 12, day: 28)\n        let iso0 = env.isoString(for: day)\n        let largeText = String(repeating: \"a\", count: 70000)\n\n        let assistant: [String: Any] = [\n            \"type\": \"assistant\",\n            \"timestamp\": iso0,\n            \"message\": [\n                \"model\": \"claude-sonnet-4-20250514\",\n                \"content\": [\n                    [\"type\": \"text\", \"text\": largeText],\n                ],\n                \"usage\": [\n                    \"input_tokens\": 3714,\n                    \"output_tokens\": 1,\n                ],\n            ],\n        ]\n        _ = try env.writeClaudeProjectFile(\n            relativePath: \"project-a/large-line.jsonl\",\n            contents: env.jsonl([assistant]))\n\n        var options = CostUsageScanner.Options(\n            codexSessionsRoot: nil,\n            claudeProjectsRoots: [env.claudeProjectsRoot],\n            cacheRoot: env.cacheRoot)\n        options.refreshMinIntervalSeconds = 0\n\n        let report = CostUsageScanner.loadDailyReport(\n            provider: .claude,\n            since: day,\n            until: day,\n            now: day,\n            options: options)\n        #expect(report.data.count == 1)\n        #expect(report.data[0].inputTokens == 3714)\n        #expect(report.data[0].outputTokens == 1)\n        #expect(report.data[0].totalTokens == 3715)\n    }\n\n    @Test\n    func `claude daily report refreshes when file changes`() throws {\n        let env = try CostUsageTestEnvironment()\n        defer { env.cleanup() }\n\n        let day = try env.makeLocalNoon(year: 2025, month: 12, day: 20)\n        let iso0 = env.isoString(for: day)\n        let iso1 = env.isoString(for: day.addingTimeInterval(1))\n\n        let model = \"claude-sonnet-4-20250514\"\n        let first: [String: Any] = [\n            \"type\": \"assistant\",\n            \"timestamp\": iso0,\n            \"message\": [\n                \"model\": model,\n                \"usage\": [\n                    \"input_tokens\": 200,\n                    \"cache_creation_input_tokens\": 50,\n                    \"cache_read_input_tokens\": 25,\n                    \"output_tokens\": 80,\n                ],\n            ],\n        ]\n        let fileURL = try env.writeClaudeProjectFile(\n            relativePath: \"project-a/session-a.jsonl\",\n            contents: env.jsonl([first]))\n\n        var options = CostUsageScanner.Options(\n            codexSessionsRoot: nil,\n            claudeProjectsRoots: [env.claudeProjectsRoot],\n            cacheRoot: env.cacheRoot)\n        options.refreshMinIntervalSeconds = 0\n\n        let firstReport = CostUsageScanner.loadDailyReport(\n            provider: .claude,\n            since: day,\n            until: day,\n            now: day,\n            options: options)\n        #expect(firstReport.data.first?.totalTokens == 355)\n\n        let second: [String: Any] = [\n            \"type\": \"assistant\",\n            \"timestamp\": iso1,\n            \"message\": [\n                \"model\": model,\n                \"usage\": [\n                    \"input_tokens\": 40,\n                    \"cache_creation_input_tokens\": 10,\n                    \"cache_read_input_tokens\": 5,\n                    \"output_tokens\": 20,\n                ],\n            ],\n        ]\n        try env.jsonl([first, second]).write(to: fileURL, atomically: true, encoding: .utf8)\n\n        let secondReport = CostUsageScanner.loadDailyReport(\n            provider: .claude,\n            since: day,\n            until: day,\n            now: day,\n            options: options)\n        #expect(secondReport.data.first?.totalTokens == 430)\n    }\n\n    @Test\n    func `codex incremental parsing uses previous totals`() throws {\n        let env = try CostUsageTestEnvironment()\n        defer { env.cleanup() }\n\n        let day = try env.makeLocalNoon(year: 2025, month: 12, day: 20)\n        let iso0 = env.isoString(for: day)\n        let iso1 = env.isoString(for: day.addingTimeInterval(1))\n        let iso2 = env.isoString(for: day.addingTimeInterval(2))\n\n        let model = \"openai/gpt-5.2-codex\"\n        let normalized = CostUsagePricing.normalizeCodexModel(model)\n        #expect(normalized == \"gpt-5.2-codex\")\n        let turnContext: [String: Any] = [\n            \"type\": \"turn_context\",\n            \"timestamp\": iso0,\n            \"payload\": [\n                \"model\": model,\n            ],\n        ]\n        let firstTokenCount: [String: Any] = [\n            \"type\": \"event_msg\",\n            \"timestamp\": iso1,\n            \"payload\": [\n                \"type\": \"token_count\",\n                \"info\": [\n                    \"total_token_usage\": [\n                        \"input_tokens\": 100,\n                        \"cached_input_tokens\": 20,\n                        \"output_tokens\": 10,\n                    ],\n                    \"model\": model,\n                ],\n            ],\n        ]\n\n        let fileURL = try env.writeCodexSessionFile(\n            day: day,\n            filename: \"session.jsonl\",\n            contents: env.jsonl([turnContext, firstTokenCount]))\n\n        let range = CostUsageScanner.CostUsageDayRange(since: day, until: day)\n        let first = CostUsageScanner.parseCodexFile(fileURL: fileURL, range: range)\n        #expect(first.parsedBytes > 0)\n        #expect(first.lastTotals?.input == 100)\n        #expect(first.lastTotals?.cached == 20)\n        #expect(first.lastTotals?.output == 10)\n\n        let secondTokenCount: [String: Any] = [\n            \"type\": \"event_msg\",\n            \"timestamp\": iso2,\n            \"payload\": [\n                \"type\": \"token_count\",\n                \"info\": [\n                    \"total_token_usage\": [\n                        \"input_tokens\": 160,\n                        \"cached_input_tokens\": 40,\n                        \"output_tokens\": 16,\n                    ],\n                    \"model\": model,\n                ],\n            ],\n        ]\n        try env.jsonl([turnContext, firstTokenCount, secondTokenCount])\n            .write(to: fileURL, atomically: true, encoding: .utf8)\n\n        let delta = CostUsageScanner.parseCodexFile(\n            fileURL: fileURL,\n            range: range,\n            startOffset: first.parsedBytes,\n            initialModel: first.lastModel,\n            initialTotals: first.lastTotals)\n        let dayKey = CostUsageScanner.CostUsageDayRange.dayKey(from: day)\n        let packed = delta.days[dayKey]?[normalized] ?? []\n        #expect(packed.count >= 3)\n        #expect(packed[0] == 60)\n        #expect(packed[1] == 20)\n        #expect(packed[2] == 6)\n    }\n\n    @Test\n    func `claude incremental parsing reads appended lines only`() throws {\n        let env = try CostUsageTestEnvironment()\n        defer { env.cleanup() }\n\n        let day = try env.makeLocalNoon(year: 2025, month: 12, day: 20)\n        let iso0 = env.isoString(for: day)\n        let iso1 = env.isoString(for: day.addingTimeInterval(1))\n\n        let model = \"claude-sonnet-4-20250514\"\n        let normalized = CostUsagePricing.normalizeClaudeModel(model)\n        let first: [String: Any] = [\n            \"type\": \"assistant\",\n            \"timestamp\": iso0,\n            \"message\": [\n                \"model\": model,\n                \"usage\": [\n                    \"input_tokens\": 200,\n                    \"cache_creation_input_tokens\": 50,\n                    \"cache_read_input_tokens\": 25,\n                    \"output_tokens\": 80,\n                ],\n            ],\n        ]\n        let fileURL = try env.writeClaudeProjectFile(\n            relativePath: \"project-a/session-a.jsonl\",\n            contents: env.jsonl([first]))\n\n        let range = CostUsageScanner.CostUsageDayRange(since: day, until: day)\n        let firstParse = CostUsageScanner.parseClaudeFile(\n            fileURL: fileURL,\n            range: range,\n            providerFilter: .all)\n        #expect(firstParse.parsedBytes > 0)\n\n        let second: [String: Any] = [\n            \"type\": \"assistant\",\n            \"timestamp\": iso1,\n            \"message\": [\n                \"model\": model,\n                \"usage\": [\n                    \"input_tokens\": 40,\n                    \"cache_creation_input_tokens\": 10,\n                    \"cache_read_input_tokens\": 5,\n                    \"output_tokens\": 20,\n                ],\n            ],\n        ]\n        try env.jsonl([first, second]).write(to: fileURL, atomically: true, encoding: .utf8)\n\n        let delta = CostUsageScanner.parseClaudeFile(\n            fileURL: fileURL,\n            range: range,\n            providerFilter: .all,\n            startOffset: firstParse.parsedBytes)\n        let dayKey = CostUsageScanner.CostUsageDayRange.dayKey(from: day)\n        let packed = delta.days[dayKey]?[normalized] ?? []\n        #expect(packed.count >= 4)\n        #expect(packed[0] == 40)\n        #expect(packed[1] == 5)\n        #expect(packed[2] == 10)\n        #expect(packed[3] == 20)\n    }\n\n    @Test\n    func `day key from timestamp matches ISO parsing`() {\n        let timestamps = [\n            \"2025-12-20T23:59:59Z\",\n            \"2025-12-20T23:59:59+02:00\",\n        ]\n\n        for ts in timestamps {\n            let expected = CostUsageScanner.dayKeyFromParsedISO(ts)\n            let fast = CostUsageScanner.dayKeyFromTimestamp(ts)\n            #expect(fast == expected)\n        }\n    }\n\n    @Test\n    func `claude deduplicates streaming chunks`() throws {\n        let env = try CostUsageTestEnvironment()\n        defer { env.cleanup() }\n\n        let day = try env.makeLocalNoon(year: 2025, month: 12, day: 20)\n        let iso0 = env.isoString(for: day)\n        let iso1 = env.isoString(for: day.addingTimeInterval(1))\n        let iso2 = env.isoString(for: day.addingTimeInterval(2))\n\n        let model = \"claude-sonnet-4-20250514\"\n        let messageId = \"msg_01ABC123\"\n        let requestId = \"req_01XYZ789\"\n\n        // Streaming emits multiple chunks with same message.id + requestId.\n        // Each chunk has cumulative usage, not delta.\n        let chunk1: [String: Any] = [\n            \"type\": \"assistant\",\n            \"timestamp\": iso0,\n            \"requestId\": requestId,\n            \"message\": [\n                \"id\": messageId,\n                \"model\": model,\n                \"usage\": [\n                    \"input_tokens\": 100,\n                    \"cache_creation_input_tokens\": 50,\n                    \"cache_read_input_tokens\": 25,\n                    \"output_tokens\": 10,\n                ],\n            ],\n        ]\n        let chunk2: [String: Any] = [\n            \"type\": \"assistant\",\n            \"timestamp\": iso1,\n            \"requestId\": requestId,\n            \"message\": [\n                \"id\": messageId,\n                \"model\": model,\n                \"usage\": [\n                    \"input_tokens\": 100,\n                    \"cache_creation_input_tokens\": 50,\n                    \"cache_read_input_tokens\": 25,\n                    \"output_tokens\": 10,\n                ],\n            ],\n        ]\n        let chunk3: [String: Any] = [\n            \"type\": \"assistant\",\n            \"timestamp\": iso2,\n            \"requestId\": requestId,\n            \"message\": [\n                \"id\": messageId,\n                \"model\": model,\n                \"usage\": [\n                    \"input_tokens\": 100,\n                    \"cache_creation_input_tokens\": 50,\n                    \"cache_read_input_tokens\": 25,\n                    \"output_tokens\": 10,\n                ],\n            ],\n        ]\n\n        _ = try env.writeClaudeProjectFile(\n            relativePath: \"project-a/session-a.jsonl\",\n            contents: env.jsonl([chunk1, chunk2, chunk3]))\n\n        var options = CostUsageScanner.Options(\n            codexSessionsRoot: nil,\n            claudeProjectsRoots: [env.claudeProjectsRoot],\n            cacheRoot: env.cacheRoot)\n        options.refreshMinIntervalSeconds = 0\n\n        let report = CostUsageScanner.loadDailyReport(\n            provider: .claude,\n            since: day,\n            until: day,\n            now: day,\n            options: options)\n\n        // Should only count once, not 3x.\n        #expect(report.data.count == 1)\n        #expect(report.data[0].inputTokens == 100)\n        #expect(report.data[0].cacheCreationTokens == 50)\n        #expect(report.data[0].cacheReadTokens == 25)\n        #expect(report.data[0].outputTokens == 10)\n        #expect(report.data[0].totalTokens == 185)\n    }\n\n    @Test\n    func `claude counts entries without ids as separate`() throws {\n        let env = try CostUsageTestEnvironment()\n        defer { env.cleanup() }\n\n        let day = try env.makeLocalNoon(year: 2025, month: 12, day: 20)\n        let iso0 = env.isoString(for: day)\n        let iso1 = env.isoString(for: day.addingTimeInterval(1))\n\n        let model = \"claude-sonnet-4-20250514\"\n\n        // Entries without message.id or requestId should still be counted\n        // (fallback for older log formats).\n        let entry1: [String: Any] = [\n            \"type\": \"assistant\",\n            \"timestamp\": iso0,\n            \"message\": [\n                \"model\": model,\n                \"usage\": [\n                    \"input_tokens\": 100,\n                    \"cache_creation_input_tokens\": 0,\n                    \"cache_read_input_tokens\": 0,\n                    \"output_tokens\": 50,\n                ],\n            ],\n        ]\n        let entry2: [String: Any] = [\n            \"type\": \"assistant\",\n            \"timestamp\": iso1,\n            \"message\": [\n                \"model\": model,\n                \"usage\": [\n                    \"input_tokens\": 200,\n                    \"cache_creation_input_tokens\": 0,\n                    \"cache_read_input_tokens\": 0,\n                    \"output_tokens\": 100,\n                ],\n            ],\n        ]\n\n        _ = try env.writeClaudeProjectFile(\n            relativePath: \"project-a/session-a.jsonl\",\n            contents: env.jsonl([entry1, entry2]))\n\n        var options = CostUsageScanner.Options(\n            codexSessionsRoot: nil,\n            claudeProjectsRoots: [env.claudeProjectsRoot],\n            cacheRoot: env.cacheRoot)\n        options.refreshMinIntervalSeconds = 0\n\n        let report = CostUsageScanner.loadDailyReport(\n            provider: .claude,\n            since: day,\n            until: day,\n            now: day,\n            options: options)\n\n        // Both entries should be counted since no IDs to dedupe.\n        #expect(report.data.count == 1)\n        #expect(report.data[0].inputTokens == 300)\n        #expect(report.data[0].outputTokens == 150)\n        #expect(report.data[0].totalTokens == 450)\n    }\n\n    @Test\n    func `claude counts different request ids separately`() throws {\n        let env = try CostUsageTestEnvironment()\n        defer { env.cleanup() }\n\n        let day = try env.makeLocalNoon(year: 2025, month: 12, day: 20)\n        let iso0 = env.isoString(for: day)\n        let iso1 = env.isoString(for: day.addingTimeInterval(1))\n\n        let model = \"claude-sonnet-4-20250514\"\n        let messageId = \"msg_01ABC123\"\n\n        let entry1: [String: Any] = [\n            \"type\": \"assistant\",\n            \"timestamp\": iso0,\n            \"requestId\": \"req_01AAA\",\n            \"message\": [\n                \"id\": messageId,\n                \"model\": model,\n                \"usage\": [\n                    \"input_tokens\": 10,\n                    \"cache_creation_input_tokens\": 0,\n                    \"cache_read_input_tokens\": 0,\n                    \"output_tokens\": 5,\n                ],\n            ],\n        ]\n        let entry2: [String: Any] = [\n            \"type\": \"assistant\",\n            \"timestamp\": iso1,\n            \"requestId\": \"req_01BBB\",\n            \"message\": [\n                \"id\": messageId,\n                \"model\": model,\n                \"usage\": [\n                    \"input_tokens\": 20,\n                    \"cache_creation_input_tokens\": 0,\n                    \"cache_read_input_tokens\": 0,\n                    \"output_tokens\": 10,\n                ],\n            ],\n        ]\n\n        _ = try env.writeClaudeProjectFile(\n            relativePath: \"project-a/session-a.jsonl\",\n            contents: env.jsonl([entry1, entry2]))\n\n        var options = CostUsageScanner.Options(\n            codexSessionsRoot: nil,\n            claudeProjectsRoots: [env.claudeProjectsRoot],\n            cacheRoot: env.cacheRoot)\n        options.refreshMinIntervalSeconds = 0\n\n        let report = CostUsageScanner.loadDailyReport(\n            provider: .claude,\n            since: day,\n            until: day,\n            now: day,\n            options: options)\n\n        #expect(report.data.count == 1)\n        #expect(report.data[0].inputTokens == 30)\n        #expect(report.data[0].outputTokens == 15)\n        #expect(report.data[0].totalTokens == 45)\n    }\n}\n\nstruct CostUsageTestEnvironment {\n    let root: URL\n    let cacheRoot: URL\n    let codexHomeRoot: URL\n    let codexSessionsRoot: URL\n    let codexArchivedSessionsRoot: URL\n    let claudeProjectsRoot: URL\n\n    init() throws {\n        let root = FileManager.default.temporaryDirectory.appendingPathComponent(\n            \"codexbar-cost-usage-\\(UUID().uuidString)\",\n            isDirectory: true)\n        try FileManager.default.createDirectory(at: root, withIntermediateDirectories: true)\n        self.root = root\n        self.cacheRoot = root.appendingPathComponent(\"cache\", isDirectory: true)\n        self.codexHomeRoot = root.appendingPathComponent(\"codex-home\", isDirectory: true)\n        self.codexSessionsRoot = self.codexHomeRoot.appendingPathComponent(\"sessions\", isDirectory: true)\n        self.codexArchivedSessionsRoot = self.codexHomeRoot\n            .appendingPathComponent(\"archived_sessions\", isDirectory: true)\n        self.claudeProjectsRoot = root.appendingPathComponent(\"claude-projects\", isDirectory: true)\n        try FileManager.default.createDirectory(at: self.cacheRoot, withIntermediateDirectories: true)\n        try FileManager.default.createDirectory(at: self.codexSessionsRoot, withIntermediateDirectories: true)\n        try FileManager.default.createDirectory(at: self.codexArchivedSessionsRoot, withIntermediateDirectories: true)\n        try FileManager.default.createDirectory(at: self.claudeProjectsRoot, withIntermediateDirectories: true)\n    }\n\n    func cleanup() {\n        try? FileManager.default.removeItem(at: self.root)\n    }\n\n    func makeLocalNoon(year: Int, month: Int, day: Int) throws -> Date {\n        var comps = DateComponents()\n        comps.calendar = Calendar.current\n        comps.timeZone = TimeZone.current\n        comps.year = year\n        comps.month = month\n        comps.day = day\n        comps.hour = 12\n        comps.minute = 0\n        comps.second = 0\n        guard let date = comps.date else { throw NSError(domain: \"CostUsageTestEnvironment\", code: 1) }\n        return date\n    }\n\n    func isoString(for date: Date) -> String {\n        let fmt = ISO8601DateFormatter()\n        fmt.formatOptions = [.withInternetDateTime]\n        return fmt.string(from: date)\n    }\n\n    func writeCodexSessionFile(day: Date, filename: String, contents: String) throws -> URL {\n        let comps = Calendar.current.dateComponents([.year, .month, .day], from: day)\n        let y = String(format: \"%04d\", comps.year ?? 1970)\n        let m = String(format: \"%02d\", comps.month ?? 1)\n        let d = String(format: \"%02d\", comps.day ?? 1)\n\n        let dir = self.codexSessionsRoot\n            .appendingPathComponent(y, isDirectory: true)\n            .appendingPathComponent(m, isDirectory: true)\n            .appendingPathComponent(d, isDirectory: true)\n        try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)\n\n        let url = dir.appendingPathComponent(filename, isDirectory: false)\n        try contents.write(to: url, atomically: true, encoding: .utf8)\n        return url\n    }\n\n    func writeClaudeProjectFile(relativePath: String, contents: String) throws -> URL {\n        let url = self.claudeProjectsRoot.appendingPathComponent(relativePath, isDirectory: false)\n        try FileManager.default.createDirectory(at: url.deletingLastPathComponent(), withIntermediateDirectories: true)\n        try contents.write(to: url, atomically: true, encoding: .utf8)\n        return url\n    }\n\n    func writeCodexArchivedSessionFile(filename: String, contents: String) throws -> URL {\n        let url = self.codexArchivedSessionsRoot.appendingPathComponent(filename, isDirectory: false)\n        try contents.write(to: url, atomically: true, encoding: .utf8)\n        return url\n    }\n\n    func jsonl(_ objects: [Any]) throws -> String {\n        let lines = try objects.map { obj in\n            let data = try JSONSerialization.data(withJSONObject: obj)\n            guard let text = String(bytes: data, encoding: .utf8) else {\n                throw NSError(domain: \"CostUsageTestEnvironment\", code: 2)\n            }\n            return text\n        }\n        return lines.joined(separator: \"\\n\") + \"\\n\"\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/CursorStatusProbeTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\nstruct CursorStatusProbeTests {\n    // MARK: - Usage Summary Parsing\n\n    @Test\n    func `parses basic usage summary`() throws {\n        let json = \"\"\"\n        {\n            \"billingCycleStart\": \"2025-01-01T00:00:00.000Z\",\n            \"billingCycleEnd\": \"2025-02-01T00:00:00.000Z\",\n            \"membershipType\": \"pro\",\n            \"individualUsage\": {\n                \"plan\": {\n                    \"enabled\": true,\n                    \"used\": 1500,\n                    \"limit\": 5000,\n                    \"remaining\": 3500,\n                    \"totalPercentUsed\": 30.0\n                },\n                \"onDemand\": {\n                    \"enabled\": true,\n                    \"used\": 500,\n                    \"limit\": 10000,\n                    \"remaining\": 9500\n                }\n            },\n            \"teamUsage\": {\n                \"onDemand\": {\n                    \"enabled\": true,\n                    \"used\": 2000,\n                    \"limit\": 50000,\n                    \"remaining\": 48000\n                }\n            }\n        }\n        \"\"\"\n        let data = try #require(json.data(using: .utf8))\n        let summary = try JSONDecoder().decode(CursorUsageSummary.self, from: data)\n\n        #expect(summary.membershipType == \"pro\")\n        #expect(summary.individualUsage?.plan?.used == 1500)\n        #expect(summary.individualUsage?.plan?.limit == 5000)\n        #expect(summary.individualUsage?.plan?.totalPercentUsed == 30.0)\n        #expect(summary.individualUsage?.onDemand?.used == 500)\n        #expect(summary.teamUsage?.onDemand?.used == 2000)\n        #expect(summary.teamUsage?.onDemand?.limit == 50000)\n    }\n\n    @Test\n    func `parses minimal usage summary`() throws {\n        let json = \"\"\"\n        {\n            \"membershipType\": \"hobby\",\n            \"individualUsage\": {\n                \"plan\": {\n                    \"used\": 0,\n                    \"limit\": 2000\n                }\n            }\n        }\n        \"\"\"\n        let data = try #require(json.data(using: .utf8))\n        let summary = try JSONDecoder().decode(CursorUsageSummary.self, from: data)\n\n        #expect(summary.membershipType == \"hobby\")\n        #expect(summary.individualUsage?.plan?.used == 0)\n        #expect(summary.individualUsage?.plan?.limit == 2000)\n        #expect(summary.teamUsage == nil)\n    }\n\n    @Test\n    func `parses enterprise usage summary`() throws {\n        let json = \"\"\"\n        {\n            \"membershipType\": \"enterprise\",\n            \"isUnlimited\": true,\n            \"individualUsage\": {\n                \"plan\": {\n                    \"enabled\": true,\n                    \"used\": 50000,\n                    \"limit\": 100000,\n                    \"totalPercentUsed\": 50.0\n                }\n            }\n        }\n        \"\"\"\n        let data = try #require(json.data(using: .utf8))\n        let summary = try JSONDecoder().decode(CursorUsageSummary.self, from: data)\n\n        #expect(summary.membershipType == \"enterprise\")\n        #expect(summary.isUnlimited == true)\n        #expect(summary.individualUsage?.plan?.totalPercentUsed == 50.0)\n    }\n\n    // MARK: - User Info Parsing\n\n    @Test\n    func `parses user info`() throws {\n        let json = \"\"\"\n        {\n            \"email\": \"user@example.com\",\n            \"email_verified\": true,\n            \"name\": \"Test User\",\n            \"sub\": \"auth0|12345\"\n        }\n        \"\"\"\n        let data = try #require(json.data(using: .utf8))\n        let userInfo = try JSONDecoder().decode(CursorUserInfo.self, from: data)\n\n        #expect(userInfo.email == \"user@example.com\")\n        #expect(userInfo.emailVerified == true)\n        #expect(userInfo.name == \"Test User\")\n        #expect(userInfo.sub == \"auth0|12345\")\n    }\n\n    // MARK: - Snapshot Conversion\n\n    @Test\n    func `prefers plan ratio over percent field`() {\n        let snapshot = CursorStatusProbe(browserDetection: BrowserDetection(cacheTTL: 0))\n            .parseUsageSummary(\n                CursorUsageSummary(\n                    billingCycleStart: nil,\n                    billingCycleEnd: nil,\n                    membershipType: \"enterprise\",\n                    limitType: nil,\n                    isUnlimited: false,\n                    autoModelSelectedDisplayMessage: nil,\n                    namedModelSelectedDisplayMessage: nil,\n                    individualUsage: CursorIndividualUsage(\n                        plan: CursorPlanUsage(\n                            enabled: true,\n                            used: 4900,\n                            limit: 50000,\n                            remaining: nil,\n                            breakdown: nil,\n                            autoPercentUsed: nil,\n                            apiPercentUsed: nil,\n                            totalPercentUsed: 0.40625),\n                        onDemand: nil),\n                    teamUsage: nil),\n                userInfo: nil,\n                rawJSON: nil)\n\n        #expect(snapshot.planPercentUsed == 9.8)\n    }\n\n    @Test\n    func `uses percent field when limit missing`() {\n        let snapshot = CursorStatusProbe(browserDetection: BrowserDetection(cacheTTL: 0))\n            .parseUsageSummary(\n                CursorUsageSummary(\n                    billingCycleStart: nil,\n                    billingCycleEnd: nil,\n                    membershipType: \"pro\",\n                    limitType: nil,\n                    isUnlimited: false,\n                    autoModelSelectedDisplayMessage: nil,\n                    namedModelSelectedDisplayMessage: nil,\n                    individualUsage: CursorIndividualUsage(\n                        plan: CursorPlanUsage(\n                            enabled: true,\n                            used: 0,\n                            limit: nil,\n                            remaining: nil,\n                            breakdown: nil,\n                            autoPercentUsed: nil,\n                            apiPercentUsed: nil,\n                            totalPercentUsed: 0.5),\n                        onDemand: nil),\n                    teamUsage: nil),\n                userInfo: nil,\n                rawJSON: nil)\n\n        #expect(snapshot.planPercentUsed == 50.0)\n    }\n\n    @Test\n    func `converts snapshot to usage snapshot`() {\n        let snapshot = CursorStatusSnapshot(\n            planPercentUsed: 45.0,\n            planUsedUSD: 22.50,\n            planLimitUSD: 50.0,\n            onDemandUsedUSD: 5.0,\n            onDemandLimitUSD: 100.0,\n            teamOnDemandUsedUSD: 25.0,\n            teamOnDemandLimitUSD: 500.0,\n            billingCycleEnd: Date(timeIntervalSince1970: 1_738_368_000), // Feb 1, 2025\n            membershipType: \"pro\",\n            accountEmail: \"user@example.com\",\n            accountName: \"Test User\",\n            rawJSON: nil)\n\n        let usageSnapshot = snapshot.toUsageSnapshot()\n\n        #expect(usageSnapshot.primary?.usedPercent == 45.0)\n        #expect(usageSnapshot.accountEmail(for: .cursor) == \"user@example.com\")\n        #expect(usageSnapshot.loginMethod(for: .cursor) == \"Cursor Pro\")\n        #expect(usageSnapshot.secondary != nil)\n        // Uses individual on-demand values (what users see in their dashboard)\n        #expect(usageSnapshot.secondary?.usedPercent == 5.0)\n        #expect(usageSnapshot.providerCost?.used == 5.0)\n        #expect(usageSnapshot.providerCost?.limit == 100.0)\n        #expect(usageSnapshot.providerCost?.currencyCode == \"USD\")\n    }\n\n    @Test\n    func `uses individual on demand when no team usage`() {\n        let snapshot = CursorStatusSnapshot(\n            planPercentUsed: 10.0,\n            planUsedUSD: 5.0,\n            planLimitUSD: 50.0,\n            onDemandUsedUSD: 12.0,\n            onDemandLimitUSD: 60.0,\n            teamOnDemandUsedUSD: nil,\n            teamOnDemandLimitUSD: nil,\n            billingCycleEnd: nil,\n            membershipType: \"pro\",\n            accountEmail: nil,\n            accountName: nil,\n            rawJSON: nil)\n\n        let usageSnapshot = snapshot.toUsageSnapshot()\n\n        #expect(usageSnapshot.secondary?.usedPercent == 20.0)\n        #expect(usageSnapshot.providerCost?.used == 12.0)\n        #expect(usageSnapshot.providerCost?.limit == 60.0)\n    }\n\n    @Test\n    func `formats membership types`() {\n        let testCases: [(input: String, expected: String)] = [\n            (\"pro\", \"Cursor Pro\"),\n            (\"hobby\", \"Cursor Hobby\"),\n            (\"enterprise\", \"Cursor Enterprise\"),\n            (\"team\", \"Cursor Team\"),\n            (\"custom\", \"Cursor Custom\"),\n        ]\n\n        for testCase in testCases {\n            let snapshot = CursorStatusSnapshot(\n                planPercentUsed: 0,\n                planUsedUSD: 0,\n                planLimitUSD: 0,\n                onDemandUsedUSD: 0,\n                onDemandLimitUSD: nil,\n                teamOnDemandUsedUSD: nil,\n                teamOnDemandLimitUSD: nil,\n                billingCycleEnd: nil,\n                membershipType: testCase.input,\n                accountEmail: nil,\n                accountName: nil,\n                rawJSON: nil)\n\n            let usageSnapshot = snapshot.toUsageSnapshot()\n            #expect(usageSnapshot.loginMethod(for: .cursor) == testCase.expected)\n        }\n    }\n\n    @Test\n    func `handles nil on demand limit`() {\n        let snapshot = CursorStatusSnapshot(\n            planPercentUsed: 50.0,\n            planUsedUSD: 25.0,\n            planLimitUSD: 50.0,\n            onDemandUsedUSD: 10.0,\n            onDemandLimitUSD: nil,\n            teamOnDemandUsedUSD: nil,\n            teamOnDemandLimitUSD: nil,\n            billingCycleEnd: nil,\n            membershipType: \"pro\",\n            accountEmail: nil,\n            accountName: nil,\n            rawJSON: nil)\n\n        let usageSnapshot = snapshot.toUsageSnapshot()\n\n        // Should still have provider cost\n        #expect(usageSnapshot.providerCost != nil)\n        #expect(usageSnapshot.providerCost?.used == 10.0)\n        #expect(usageSnapshot.providerCost?.limit == 0.0)\n        // Secondary should be nil when no on-demand limit\n        #expect(usageSnapshot.secondary == nil)\n    }\n\n    // MARK: - Legacy Request-Based Plan\n\n    @Test\n    func `parses legacy request based plan`() {\n        let snapshot = CursorStatusSnapshot(\n            planPercentUsed: 100.0,\n            planUsedUSD: 0,\n            planLimitUSD: 0,\n            onDemandUsedUSD: 43.64,\n            onDemandLimitUSD: 200.0,\n            teamOnDemandUsedUSD: 92.91,\n            teamOnDemandLimitUSD: 20000.0,\n            billingCycleEnd: nil,\n            membershipType: \"enterprise\",\n            accountEmail: \"user@company.com\",\n            accountName: \"Test User\",\n            rawJSON: nil,\n            requestsUsed: 500,\n            requestsLimit: 500)\n\n        #expect(snapshot.isLegacyRequestPlan == true)\n        #expect(snapshot.requestsUsed == 500)\n        #expect(snapshot.requestsLimit == 500)\n\n        let usageSnapshot = snapshot.toUsageSnapshot()\n\n        #expect(usageSnapshot.cursorRequests != nil)\n        #expect(usageSnapshot.cursorRequests?.used == 500)\n        #expect(usageSnapshot.cursorRequests?.limit == 500)\n        #expect(usageSnapshot.cursorRequests?.usedPercent == 100.0)\n        #expect(usageSnapshot.cursorRequests?.remainingPercent == 0.0)\n\n        // Primary RateWindow should use request-based percentage for legacy plans\n        #expect(usageSnapshot.primary?.usedPercent == 100.0)\n    }\n\n    @Test\n    func `legacy plan primary uses requests not dollars`() {\n        // Regression: Legacy plans report planPercentUsed as 0 while requests are used\n        let snapshot = CursorStatusSnapshot(\n            planPercentUsed: 0.0, // Dollar-based shows 0\n            planUsedUSD: 0,\n            planLimitUSD: 0,\n            onDemandUsedUSD: 0,\n            onDemandLimitUSD: nil,\n            teamOnDemandUsedUSD: nil,\n            teamOnDemandLimitUSD: nil,\n            billingCycleEnd: nil,\n            membershipType: \"enterprise\",\n            accountEmail: \"user@company.com\",\n            accountName: nil,\n            rawJSON: nil,\n            requestsUsed: 250,\n            requestsLimit: 500)\n\n        #expect(snapshot.isLegacyRequestPlan == true)\n\n        let usageSnapshot = snapshot.toUsageSnapshot()\n\n        // Primary should reflect request usage (50%), not dollar usage (0%)\n        #expect(usageSnapshot.primary?.usedPercent == 50.0)\n        #expect(usageSnapshot.cursorRequests?.usedPercent == 50.0)\n    }\n\n    @Test\n    func `parse usage summary prefers request total`() {\n        let summary = CursorUsageSummary(\n            billingCycleStart: nil,\n            billingCycleEnd: nil,\n            membershipType: nil,\n            limitType: nil,\n            isUnlimited: nil,\n            autoModelSelectedDisplayMessage: nil,\n            namedModelSelectedDisplayMessage: nil,\n            individualUsage: nil,\n            teamUsage: nil)\n        let requestUsage = CursorUsageResponse(\n            gpt4: CursorModelUsage(\n                numRequests: 120,\n                numRequestsTotal: 240,\n                numTokens: nil,\n                maxRequestUsage: 500,\n                maxTokenUsage: nil),\n            startOfMonth: nil)\n\n        let snapshot = CursorStatusProbe(browserDetection: BrowserDetection(cacheTTL: 0)).parseUsageSummary(\n            summary,\n            userInfo: nil,\n            rawJSON: nil,\n            requestUsage: requestUsage)\n\n        #expect(snapshot.requestsUsed == 240)\n        #expect(snapshot.requestsLimit == 500)\n    }\n\n    @Test\n    func `detects non legacy plan`() {\n        let snapshot = CursorStatusSnapshot(\n            planPercentUsed: 50.0,\n            planUsedUSD: 25.0,\n            planLimitUSD: 50.0,\n            onDemandUsedUSD: 0,\n            onDemandLimitUSD: 100.0,\n            teamOnDemandUsedUSD: nil,\n            teamOnDemandLimitUSD: nil,\n            billingCycleEnd: nil,\n            membershipType: \"pro\",\n            accountEmail: nil,\n            accountName: nil,\n            rawJSON: nil)\n\n        #expect(snapshot.isLegacyRequestPlan == false)\n        #expect(snapshot.requestsUsed == nil)\n        #expect(snapshot.requestsLimit == nil)\n\n        let usageSnapshot = snapshot.toUsageSnapshot()\n        #expect(usageSnapshot.cursorRequests == nil)\n    }\n\n    // MARK: - Session Store Serialization\n\n    @Test\n    func `session store saves and loads cookies`() async {\n        let store = CursorSessionStore.shared\n\n        // Clear any existing cookies\n        await store.clearCookies()\n\n        // Create test cookies with Date properties\n        let cookieProps: [HTTPCookiePropertyKey: Any] = [\n            .name: \"testCookie\",\n            .value: \"testValue\",\n            .domain: \"cursor.com\",\n            .path: \"/\",\n            .expires: Date(timeIntervalSince1970: 1_800_000_000),\n            .secure: true,\n        ]\n\n        guard let cookie = HTTPCookie(properties: cookieProps) else {\n            Issue.record(\"Failed to create test cookie\")\n            return\n        }\n\n        // Save cookies\n        await store.setCookies([cookie])\n\n        // Verify cookies are stored\n        let storedCookies = await store.getCookies()\n        #expect(storedCookies.count == 1)\n        #expect(storedCookies.first?.name == \"testCookie\")\n        #expect(storedCookies.first?.value == \"testValue\")\n\n        // Clean up\n        await store.clearCookies()\n    }\n\n    @Test\n    func `session store reloads from disk when needed`() async {\n        let store = CursorSessionStore.shared\n        await store.resetForTesting()\n\n        let cookieProps: [HTTPCookiePropertyKey: Any] = [\n            .name: \"diskCookie\",\n            .value: \"diskValue\",\n            .domain: \"cursor.com\",\n            .path: \"/\",\n            .expires: Date(timeIntervalSince1970: 1_800_000_000),\n            .secure: true,\n        ]\n\n        guard let cookie = HTTPCookie(properties: cookieProps) else {\n            Issue.record(\"Failed to create test cookie\")\n            return\n        }\n\n        await store.setCookies([cookie])\n        await store.resetForTesting(clearDisk: false)\n\n        let reloaded = await store.getCookies()\n        #expect(reloaded.count == 1)\n        #expect(reloaded.first?.name == \"diskCookie\")\n        #expect(reloaded.first?.value == \"diskValue\")\n\n        await store.clearCookies()\n    }\n\n    @Test\n    func `session store has valid session loads from disk`() async {\n        let store = CursorSessionStore.shared\n        await store.resetForTesting()\n\n        let cookieProps: [HTTPCookiePropertyKey: Any] = [\n            .name: \"validCookie\",\n            .value: \"validValue\",\n            .domain: \"cursor.com\",\n            .path: \"/\",\n            .expires: Date(timeIntervalSince1970: 1_800_000_000),\n            .secure: true,\n        ]\n\n        guard let cookie = HTTPCookie(properties: cookieProps) else {\n            Issue.record(\"Failed to create test cookie\")\n            return\n        }\n\n        await store.setCookies([cookie])\n        await store.resetForTesting(clearDisk: false)\n\n        let hasSession = await store.hasValidSession()\n        #expect(hasSession)\n\n        await store.clearCookies()\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/FactoryStatusProbeFetchTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n\n@Suite(.serialized)\nstruct FactoryStatusProbeFetchTests {\n    @Test\n    func `fetches snapshot using cookie header override`() async throws {\n        let registered = URLProtocol.registerClass(FactoryStubURLProtocol.self)\n        defer {\n            if registered {\n                URLProtocol.unregisterClass(FactoryStubURLProtocol.self)\n            }\n            FactoryStubURLProtocol.handler = nil\n        }\n\n        FactoryStubURLProtocol.handler = { request in\n            guard let url = request.url else { throw URLError(.badURL) }\n            let path = url.path\n            if path == \"/api/app/auth/me\" {\n                let body = \"\"\"\n                {\n                  \"organization\": {\n                    \"id\": \"org_1\",\n                    \"name\": \"Acme\",\n                    \"subscription\": {\n                      \"factoryTier\": \"team\",\n                      \"orbSubscription\": {\n                        \"plan\": { \"name\": \"Team\", \"id\": \"plan_1\" },\n                        \"status\": \"active\"\n                      }\n                    }\n                  }\n                }\n                \"\"\"\n                return Self.makeResponse(url: url, body: body)\n            }\n            if path == \"/api/organization/subscription/usage\" {\n                let body = \"\"\"\n                {\n                  \"usage\": {\n                    \"startDate\": 1700000000000,\n                    \"endDate\": 1700003600000,\n                    \"standard\": {\n                      \"userTokens\": 100,\n                      \"orgTotalTokensUsed\": 250,\n                      \"totalAllowance\": 1000,\n                      \"usedRatio\": 0.10\n                    },\n                    \"premium\": {\n                      \"userTokens\": 10,\n                      \"orgTotalTokensUsed\": 20,\n                      \"totalAllowance\": 100,\n                      \"usedRatio\": 0.10\n                    }\n                  },\n                  \"userId\": \"user-1\"\n                }\n                \"\"\"\n                return Self.makeResponse(url: url, body: body)\n            }\n            return Self.makeResponse(url: url, body: \"{}\", statusCode: 404)\n        }\n\n        let probe = FactoryStatusProbe(browserDetection: BrowserDetection(cacheTTL: 0))\n        let snapshot = try await probe.fetch(cookieHeaderOverride: \"access-token=test.jwt.token; session=abc\")\n\n        #expect(snapshot.standardUserTokens == 100)\n        #expect(snapshot.standardAllowance == 1000)\n        #expect(snapshot.standardUsedRatio == 0.10)\n        #expect(snapshot.premiumUserTokens == 10)\n        #expect(snapshot.premiumUsedRatio == 0.10)\n        #expect(snapshot.userId == \"user-1\")\n        #expect(snapshot.planName == \"Team\")\n\n        let usage = snapshot.toUsageSnapshot()\n        #expect(usage.primary?.usedPercent == 10)\n        #expect(usage.secondary?.usedPercent == 10)\n    }\n\n    private static func makeResponse(\n        url: URL,\n        body: String,\n        statusCode: Int = 200) -> (HTTPURLResponse, Data)\n    {\n        let response = HTTPURLResponse(\n            url: url,\n            statusCode: statusCode,\n            httpVersion: \"HTTP/1.1\",\n            headerFields: [\"Content-Type\": \"application/json\"])!\n        return (response, Data(body.utf8))\n    }\n}\n\nfinal class FactoryStubURLProtocol: URLProtocol {\n    nonisolated(unsafe) static var handler: ((URLRequest) throws -> (HTTPURLResponse, Data))?\n\n    override static func canInit(with request: URLRequest) -> Bool {\n        guard let host = request.url?.host else { return false }\n        return host.hasSuffix(\"factory.ai\") || host == \"api.workos.com\"\n    }\n\n    override static func canonicalRequest(for request: URLRequest) -> URLRequest {\n        request\n    }\n\n    override func startLoading() {\n        guard let handler = Self.handler else {\n            self.client?.urlProtocol(self, didFailWithError: URLError(.badServerResponse))\n            return\n        }\n        do {\n            let (response, data) = try handler(self.request)\n            self.client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)\n            self.client?.urlProtocol(self, didLoad: data)\n            self.client?.urlProtocolDidFinishLoading(self)\n        } catch {\n            self.client?.urlProtocol(self, didFailWithError: error)\n        }\n    }\n\n    override func stopLoading() {}\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/FactoryStatusProbeTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\nstruct FactoryStatusSnapshotTests {\n    @Test\n    func `maps usage snapshot windows and login method`() {\n        let periodEnd = Date(timeIntervalSince1970: 1_738_368_000) // Feb 1, 2025\n        let snapshot = FactoryStatusSnapshot(\n            standardUserTokens: 50,\n            standardOrgTokens: 0,\n            standardAllowance: 100,\n            premiumUserTokens: 25,\n            premiumOrgTokens: 0,\n            premiumAllowance: 50,\n            periodStart: nil,\n            periodEnd: periodEnd,\n            planName: \"Pro\",\n            tier: \"enterprise\",\n            organizationName: \"Acme\",\n            accountEmail: \"user@example.com\",\n            userId: \"user-1\",\n            rawJSON: nil)\n\n        let usage = snapshot.toUsageSnapshot()\n\n        #expect(usage.primary?.usedPercent == 50)\n        #expect(usage.primary?.resetsAt == periodEnd)\n        #expect(usage.primary?.resetDescription?.hasPrefix(\"Resets \") == true)\n        #expect(usage.secondary?.usedPercent == 50)\n        #expect(usage.loginMethod(for: .factory) == \"Factory Enterprise - Pro\")\n    }\n\n    @Test\n    func `treats large allowances as unlimited`() {\n        let snapshot = FactoryStatusSnapshot(\n            standardUserTokens: 50_000_000,\n            standardOrgTokens: 0,\n            standardAllowance: 2_000_000_000_000,\n            premiumUserTokens: 0,\n            premiumOrgTokens: 0,\n            premiumAllowance: 0,\n            periodStart: nil,\n            periodEnd: nil,\n            planName: nil,\n            tier: nil,\n            organizationName: nil,\n            accountEmail: nil,\n            userId: nil,\n            rawJSON: nil)\n\n        let usage = snapshot.toUsageSnapshot()\n\n        #expect(usage.primary?.usedPercent == 50)\n    }\n\n    @Test\n    func `prefers API used ratio when allowance missing`() {\n        let snapshot = FactoryStatusSnapshot(\n            standardUserTokens: 72_311_737,\n            standardOrgTokens: 72_311_737,\n            standardAllowance: 0,\n            standardUsedRatio: 0.3615586850,\n            premiumUserTokens: 0,\n            premiumOrgTokens: 0,\n            premiumAllowance: 0,\n            premiumUsedRatio: 0.0,\n            periodStart: nil,\n            periodEnd: nil,\n            planName: \"Max\",\n            tier: nil,\n            organizationName: nil,\n            accountEmail: nil,\n            userId: nil,\n            rawJSON: nil)\n\n        let usage = snapshot.toUsageSnapshot()\n\n        #expect(usage.primary?.usedPercent ?? 0 > 36)\n        #expect(usage.primary?.usedPercent ?? 0 < 37)\n    }\n\n    @Test\n    func `uses percent scale ratio when allowance missing`() {\n        let snapshot = FactoryStatusSnapshot(\n            standardUserTokens: 0,\n            standardOrgTokens: 0,\n            standardAllowance: 0,\n            standardUsedRatio: 10.0,\n            premiumUserTokens: 0,\n            premiumOrgTokens: 0,\n            premiumAllowance: 0,\n            premiumUsedRatio: nil,\n            periodStart: nil,\n            periodEnd: nil,\n            planName: nil,\n            tier: nil,\n            organizationName: nil,\n            accountEmail: nil,\n            userId: nil,\n            rawJSON: nil)\n\n        let usage = snapshot.toUsageSnapshot()\n\n        #expect(usage.primary?.usedPercent == 10)\n    }\n\n    @Test\n    func `falls back to calculation when API ratio missing`() {\n        let snapshot = FactoryStatusSnapshot(\n            standardUserTokens: 50_000_000,\n            standardOrgTokens: 0,\n            standardAllowance: 100_000_000,\n            standardUsedRatio: nil,\n            premiumUserTokens: 0,\n            premiumOrgTokens: 0,\n            premiumAllowance: 0,\n            premiumUsedRatio: nil,\n            periodStart: nil,\n            periodEnd: nil,\n            planName: nil,\n            tier: nil,\n            organizationName: nil,\n            accountEmail: nil,\n            userId: nil,\n            rawJSON: nil)\n\n        let usage = snapshot.toUsageSnapshot()\n\n        #expect(usage.primary?.usedPercent == 50)\n    }\n\n    @Test\n    func `falls back when API ratio is invalid`() {\n        let snapshot = FactoryStatusSnapshot(\n            standardUserTokens: 50_000_000,\n            standardOrgTokens: 0,\n            standardAllowance: 100_000_000,\n            standardUsedRatio: 1.5,\n            premiumUserTokens: 0,\n            premiumOrgTokens: 0,\n            premiumAllowance: 0,\n            premiumUsedRatio: -0.5,\n            periodStart: nil,\n            periodEnd: nil,\n            planName: nil,\n            tier: nil,\n            organizationName: nil,\n            accountEmail: nil,\n            userId: nil,\n            rawJSON: nil)\n\n        let usage = snapshot.toUsageSnapshot()\n\n        #expect(usage.primary?.usedPercent == 50)\n    }\n\n    @Test\n    func `clamps slightly out of range ratios`() {\n        let snapshot = FactoryStatusSnapshot(\n            standardUserTokens: 100_000_000,\n            standardOrgTokens: 0,\n            standardAllowance: 100_000_000,\n            standardUsedRatio: 1.0005,\n            premiumUserTokens: 0,\n            premiumOrgTokens: 0,\n            premiumAllowance: 0,\n            premiumUsedRatio: nil,\n            periodStart: nil,\n            periodEnd: nil,\n            planName: nil,\n            tier: nil,\n            organizationName: nil,\n            accountEmail: nil,\n            userId: nil,\n            rawJSON: nil)\n\n        let usage = snapshot.toUsageSnapshot()\n\n        #expect(usage.primary?.usedPercent == 100)\n    }\n}\n\nstruct FactoryStatusProbeWorkOSTests {\n    @Test\n    func `detects missing refresh token payload`() {\n        let payload = Data(\"\"\"\n        {\"error\":\"invalid_request\",\"error_description\":\"Missing refresh token.\"}\n        \"\"\".utf8)\n\n        #expect(FactoryStatusProbe.isMissingWorkOSRefreshToken(payload))\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/GeminiAPITestHelpers.swift",
    "content": "import Foundation\n\nenum GeminiAPITestHelpers {\n    static func dataLoader(\n        handler: @escaping @Sendable (URLRequest) throws -> (HTTPURLResponse, Data))\n        -> @Sendable (URLRequest) async throws -> (Data, URLResponse)\n    {\n        { request in\n            let (response, data) = try handler(request)\n            return (data, response)\n        }\n    }\n\n    static func response(url: String, status: Int, body: Data) -> (HTTPURLResponse, Data) {\n        let response = HTTPURLResponse(\n            url: URL(string: url)!,\n            statusCode: status,\n            httpVersion: nil,\n            headerFields: [\"Content-Type\": \"application/json\"])!\n        return (response, body)\n    }\n\n    static func jsonData(_ payload: [String: Any]) -> Data {\n        (try? JSONSerialization.data(withJSONObject: payload)) ?? Data()\n    }\n\n    static func sampleQuotaResponse() -> Data {\n        self.jsonData([\n            \"buckets\": [\n                [\n                    \"modelId\": \"gemini-2.5-pro\",\n                    \"remainingFraction\": 0.6,\n                    \"resetTime\": \"2025-01-01T00:00:00Z\",\n                ],\n                [\n                    \"modelId\": \"gemini-2.5-flash\",\n                    \"remainingFraction\": 0.9,\n                    \"resetTime\": \"2025-01-01T00:00:00Z\",\n                ],\n                [\n                    \"modelId\": \"gemini-2.5-flash-lite\",\n                    \"remainingFraction\": 0.8,\n                    \"resetTime\": \"2025-01-01T00:00:00Z\",\n                ],\n            ],\n        ])\n    }\n\n    static func sampleFlashQuotaResponse() -> Data {\n        self.jsonData([\n            \"buckets\": [\n                [\n                    \"modelId\": \"gemini-2.5-flash\",\n                    \"remainingFraction\": 0.9,\n                    \"resetTime\": \"2025-01-01T00:00:00Z\",\n                ],\n                [\n                    \"modelId\": \"gemini-2.5-flash\",\n                    \"remainingFraction\": 0.4,\n                    \"resetTime\": \"2025-01-01T00:00:00Z\",\n                ],\n            ],\n        ])\n    }\n\n    static func makeIDToken(email: String, hostedDomain: String? = nil) -> String {\n        var payload: [String: Any] = [\"email\": email]\n        if let hd = hostedDomain {\n            payload[\"hd\"] = hd\n        }\n        let data = (try? JSONSerialization.data(withJSONObject: payload)) ?? Data()\n        var encoded = data.base64EncodedString()\n        encoded = encoded.replacingOccurrences(of: \"+\", with: \"-\")\n        encoded = encoded.replacingOccurrences(of: \"/\", with: \"_\")\n        encoded = encoded.replacingOccurrences(of: \"=\", with: \"\")\n        return \"header.\\(encoded).sig\"\n    }\n\n    static func loadCodeAssistResponse(tierId: String, projectId: String? = nil) -> Data {\n        var payload: [String: Any] = [\n            \"currentTier\": [\n                \"id\": tierId,\n                \"name\": tierId.replacingOccurrences(of: \"-tier\", with: \"\"),\n            ],\n        ]\n        if let projectId {\n            payload[\"cloudaicompanionProject\"] = projectId\n        }\n        return self.jsonData(payload)\n    }\n\n    static func loadCodeAssistFreeTierResponse() -> Data {\n        self.loadCodeAssistResponse(tierId: \"free-tier\")\n    }\n\n    static func loadCodeAssistStandardTierResponse() -> Data {\n        self.loadCodeAssistResponse(tierId: \"standard-tier\")\n    }\n\n    static func loadCodeAssistLegacyTierResponse() -> Data {\n        self.loadCodeAssistResponse(tierId: \"legacy-tier\")\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/GeminiLoginAlertTests.swift",
    "content": "import Testing\n@testable import CodexBar\n\nstruct GeminiLoginAlertTests {\n    @Test\n    func `returns alert for missing binary`() {\n        let result = GeminiLoginRunner.Result(outcome: .missingBinary)\n        let info = StatusItemController.geminiLoginAlertInfo(for: result)\n        #expect(info?.title == \"Gemini CLI not found\")\n        #expect(info?.message == \"Install the Gemini CLI (npm i -g @google/gemini-cli) and try again.\")\n    }\n\n    @Test\n    func `returns alert for launch failure`() {\n        let result = GeminiLoginRunner.Result(outcome: .launchFailed(\"Boom\"))\n        let info = StatusItemController.geminiLoginAlertInfo(for: result)\n        #expect(info?.title == \"Could not open Terminal for Gemini\")\n        #expect(info?.message == \"Boom\")\n    }\n\n    @Test\n    func `returns nil on success`() {\n        let result = GeminiLoginRunner.Result(outcome: .success)\n        let info = StatusItemController.geminiLoginAlertInfo(for: result)\n        #expect(info == nil)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/GeminiMenuCardTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n@testable import CodexBar\n\nstruct GeminiMenuCardTests {\n    @Test\n    func `gemini model uses flash lite title for tertiary metric`() throws {\n        let now = Date()\n        let identity = ProviderIdentitySnapshot(\n            providerID: .gemini,\n            accountEmail: \"gemini@example.com\",\n            accountOrganization: nil,\n            loginMethod: \"Paid\")\n        let snapshot = UsageSnapshot(\n            primary: RateWindow(\n                usedPercent: 0,\n                windowMinutes: 1440,\n                resetsAt: now.addingTimeInterval(3600),\n                resetDescription: \"Resets in 1h\"),\n            secondary: RateWindow(\n                usedPercent: 25,\n                windowMinutes: 1440,\n                resetsAt: now.addingTimeInterval(7200),\n                resetDescription: \"Resets in 2h\"),\n            tertiary: RateWindow(\n                usedPercent: 40,\n                windowMinutes: 1440,\n                resetsAt: now.addingTimeInterval(10800),\n                resetDescription: \"Resets in 3h\"),\n            updatedAt: now,\n            identity: identity)\n        let metadata = try #require(ProviderDefaults.metadata[.gemini])\n\n        let model = UsageMenuCardView.Model.make(.init(\n            provider: .gemini,\n            metadata: metadata,\n            snapshot: snapshot,\n            credits: nil,\n            creditsError: nil,\n            dashboard: nil,\n            dashboardError: nil,\n            tokenSnapshot: nil,\n            tokenError: nil,\n            account: AccountInfo(email: \"gemini@example.com\", plan: \"Paid\"),\n            isRefreshing: false,\n            lastError: nil,\n            usageBarsShowUsed: false,\n            resetTimeDisplayStyle: .countdown,\n            tokenCostUsageEnabled: false,\n            showOptionalCreditsAndExtraUsage: true,\n            hidePersonalInfo: false,\n            now: now))\n\n        #expect(model.metrics.map(\\.title) == [\"Pro\", \"Flash\", \"Flash Lite\"])\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/GeminiStatusProbeAPITests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n\n@Suite(.serialized)\nstruct GeminiStatusProbeAPITests {\n    @Test\n    func `missing credentials throws not logged in`() async throws {\n        let env = try GeminiTestEnvironment()\n        defer { env.cleanup() }\n\n        let probe = GeminiStatusProbe(timeout: 1, homeDirectory: env.homeURL.path)\n        await Self.expectError(.notLoggedIn) {\n            _ = try await probe.fetch()\n        }\n    }\n\n    @Test\n    func `rejects api key auth type`() async throws {\n        let env = try GeminiTestEnvironment()\n        defer { env.cleanup() }\n        try env.writeSettings(authType: \"api-key\")\n\n        let probe = GeminiStatusProbe(timeout: 1, homeDirectory: env.homeURL.path)\n        await Self.expectError(.unsupportedAuthType(\"API key\")) {\n            _ = try await probe.fetch()\n        }\n    }\n\n    @Test\n    func `rejects vertex auth type`() async throws {\n        let env = try GeminiTestEnvironment()\n        defer { env.cleanup() }\n        try env.writeSettings(authType: \"vertex-ai\")\n\n        let probe = GeminiStatusProbe(timeout: 1, homeDirectory: env.homeURL.path)\n        await Self.expectError(.unsupportedAuthType(\"Vertex AI\")) {\n            _ = try await probe.fetch()\n        }\n    }\n\n    @Test\n    func `refreshes expired token and updates stored credentials`() async throws {\n        let env = try GeminiTestEnvironment()\n        defer { env.cleanup() }\n        try env.writeCredentials(\n            accessToken: \"old-token\",\n            refreshToken: \"refresh-token\",\n            expiry: Date().addingTimeInterval(-3600),\n            idToken: GeminiAPITestHelpers.makeIDToken(email: \"user@example.com\"))\n\n        let binURL = try env.writeFakeGeminiCLI()\n        let previousValue = ProcessInfo.processInfo.environment[\"GEMINI_CLI_PATH\"]\n        setenv(\"GEMINI_CLI_PATH\", binURL.path, 1)\n        defer {\n            if let previousValue {\n                setenv(\"GEMINI_CLI_PATH\", previousValue, 1)\n            } else {\n                unsetenv(\"GEMINI_CLI_PATH\")\n            }\n        }\n\n        let dataLoader = GeminiAPITestHelpers.dataLoader { request in\n            guard let url = request.url, let host = url.host else {\n                throw URLError(.badURL)\n            }\n\n            switch host {\n            case \"oauth2.googleapis.com\":\n                let json = GeminiAPITestHelpers.jsonData([\n                    \"access_token\": \"new-token\",\n                    \"expires_in\": 3600,\n                    \"id_token\": GeminiAPITestHelpers.makeIDToken(email: \"user@example.com\"),\n                ])\n                return GeminiAPITestHelpers.response(url: url.absoluteString, status: 200, body: json)\n            case \"cloudresourcemanager.googleapis.com\":\n                let json = GeminiAPITestHelpers.jsonData([\"projects\": []])\n                return GeminiAPITestHelpers.response(url: url.absoluteString, status: 200, body: json)\n            case \"cloudcode-pa.googleapis.com\":\n                if url.path == \"/v1internal:loadCodeAssist\" {\n                    let auth = request.value(forHTTPHeaderField: \"Authorization\")\n                    if auth != \"Bearer new-token\" {\n                        return GeminiAPITestHelpers.response(url: url.absoluteString, status: 401, body: Data())\n                    }\n                    return GeminiAPITestHelpers.response(\n                        url: url.absoluteString,\n                        status: 200,\n                        body: GeminiAPITestHelpers.loadCodeAssistStandardTierResponse())\n                }\n                if url.path != \"/v1internal:retrieveUserQuota\" {\n                    return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data())\n                }\n                let auth = request.value(forHTTPHeaderField: \"Authorization\")\n                if auth != \"Bearer new-token\" {\n                    return GeminiAPITestHelpers.response(url: url.absoluteString, status: 401, body: Data())\n                }\n                return GeminiAPITestHelpers.response(\n                    url: url.absoluteString,\n                    status: 200,\n                    body: GeminiAPITestHelpers.sampleQuotaResponse())\n            default:\n                return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data())\n            }\n        }\n\n        let probe = GeminiStatusProbe(timeout: 2, homeDirectory: env.homeURL.path, dataLoader: dataLoader)\n        let snapshot = try await probe.fetch()\n        #expect(snapshot.accountPlan == \"Paid\")\n\n        let updated = try env.readCredentials()\n        #expect(updated[\"access_token\"] as? String == \"new-token\")\n    }\n\n    @Test\n    func `refreshes expired token with nix share layout`() async throws {\n        let env = try GeminiTestEnvironment()\n        defer { env.cleanup() }\n        try env.writeCredentials(\n            accessToken: \"old-token\",\n            refreshToken: \"refresh-token\",\n            expiry: Date().addingTimeInterval(-3600),\n            idToken: GeminiAPITestHelpers.makeIDToken(email: \"user@example.com\"))\n\n        let binURL = try env.writeFakeGeminiCLI(layout: .nixShare)\n        let previousValue = ProcessInfo.processInfo.environment[\"GEMINI_CLI_PATH\"]\n        setenv(\"GEMINI_CLI_PATH\", binURL.path, 1)\n        defer {\n            if let previousValue {\n                setenv(\"GEMINI_CLI_PATH\", previousValue, 1)\n            } else {\n                unsetenv(\"GEMINI_CLI_PATH\")\n            }\n        }\n\n        let dataLoader = GeminiAPITestHelpers.dataLoader { request in\n            guard let url = request.url, let host = url.host else {\n                throw URLError(.badURL)\n            }\n\n            switch host {\n            case \"oauth2.googleapis.com\":\n                let json = GeminiAPITestHelpers.jsonData([\n                    \"access_token\": \"new-token\",\n                    \"expires_in\": 3600,\n                    \"id_token\": GeminiAPITestHelpers.makeIDToken(email: \"user@example.com\"),\n                ])\n                return GeminiAPITestHelpers.response(url: url.absoluteString, status: 200, body: json)\n            case \"cloudresourcemanager.googleapis.com\":\n                let json = GeminiAPITestHelpers.jsonData([\"projects\": []])\n                return GeminiAPITestHelpers.response(url: url.absoluteString, status: 200, body: json)\n            case \"cloudcode-pa.googleapis.com\":\n                if url.path == \"/v1internal:loadCodeAssist\" {\n                    let auth = request.value(forHTTPHeaderField: \"Authorization\")\n                    if auth != \"Bearer new-token\" {\n                        return GeminiAPITestHelpers.response(url: url.absoluteString, status: 401, body: Data())\n                    }\n                    return GeminiAPITestHelpers.response(\n                        url: url.absoluteString,\n                        status: 200,\n                        body: GeminiAPITestHelpers.loadCodeAssistStandardTierResponse())\n                }\n                if url.path != \"/v1internal:retrieveUserQuota\" {\n                    return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data())\n                }\n                let auth = request.value(forHTTPHeaderField: \"Authorization\")\n                if auth != \"Bearer new-token\" {\n                    return GeminiAPITestHelpers.response(url: url.absoluteString, status: 401, body: Data())\n                }\n                return GeminiAPITestHelpers.response(\n                    url: url.absoluteString,\n                    status: 200,\n                    body: GeminiAPITestHelpers.sampleQuotaResponse())\n            default:\n                return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data())\n            }\n        }\n\n        let probe = GeminiStatusProbe(timeout: 2, homeDirectory: env.homeURL.path, dataLoader: dataLoader)\n        let snapshot = try await probe.fetch()\n        #expect(snapshot.accountPlan == \"Paid\")\n    }\n\n    @Test\n    func `uses code assist project for quota`() async throws {\n        let env = try GeminiTestEnvironment()\n        defer { env.cleanup() }\n        try env.writeCredentials(\n            accessToken: \"token\",\n            refreshToken: nil,\n            expiry: Date().addingTimeInterval(3600),\n            idToken: nil)\n\n        final class ProjectCapture: @unchecked Sendable {\n            private let lock = NSLock()\n            private var value: String?\n\n            func set(_ newValue: String?) {\n                self.lock.lock()\n                self.value = newValue\n                self.lock.unlock()\n            }\n\n            func get() -> String? {\n                self.lock.lock()\n                defer { self.lock.unlock() }\n                return self.value\n            }\n        }\n\n        let projectId = \"managed-project-123\"\n        let seenProject = ProjectCapture()\n\n        let dataLoader = GeminiAPITestHelpers.dataLoader { request in\n            guard let url = request.url, let host = url.host else {\n                throw URLError(.badURL)\n            }\n\n            switch host {\n            case \"cloudcode-pa.googleapis.com\":\n                if url.path == \"/v1internal:loadCodeAssist\" {\n                    return GeminiAPITestHelpers.response(\n                        url: url.absoluteString,\n                        status: 200,\n                        body: GeminiAPITestHelpers.loadCodeAssistResponse(\n                            tierId: \"free-tier\",\n                            projectId: projectId))\n                }\n                if url.path == \"/v1internal:retrieveUserQuota\" {\n                    if let body = request.httpBody,\n                       let json = try? JSONSerialization.jsonObject(with: body) as? [String: Any]\n                    {\n                        seenProject.set(json[\"project\"] as? String)\n                    }\n                    return GeminiAPITestHelpers.response(\n                        url: url.absoluteString,\n                        status: 200,\n                        body: GeminiAPITestHelpers.sampleQuotaResponse())\n                }\n                return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data())\n            case \"cloudresourcemanager.googleapis.com\":\n                return GeminiAPITestHelpers.response(url: url.absoluteString, status: 500, body: Data())\n            default:\n                return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data())\n            }\n        }\n\n        let probe = GeminiStatusProbe(timeout: 1, homeDirectory: env.homeURL.path, dataLoader: dataLoader)\n        _ = try await probe.fetch()\n        #expect(seenProject.get() == projectId)\n    }\n\n    @Test\n    func `fails refresh when O auth config missing`() async throws {\n        let env = try GeminiTestEnvironment()\n        defer { env.cleanup() }\n        try env.writeCredentials(\n            accessToken: \"old-token\",\n            refreshToken: \"refresh-token\",\n            expiry: Date().addingTimeInterval(-3600),\n            idToken: nil)\n\n        let binURL = try env.writeFakeGeminiCLI(includeOAuth: false)\n        let previousValue = ProcessInfo.processInfo.environment[\"GEMINI_CLI_PATH\"]\n        setenv(\"GEMINI_CLI_PATH\", binURL.path, 1)\n        defer {\n            if let previousValue {\n                setenv(\"GEMINI_CLI_PATH\", previousValue, 1)\n            } else {\n                unsetenv(\"GEMINI_CLI_PATH\")\n            }\n        }\n\n        let probe = GeminiStatusProbe(timeout: 1, homeDirectory: env.homeURL.path)\n        await Self.expectError(.apiError(\"Could not find Gemini CLI OAuth configuration\")) {\n            _ = try await probe.fetch()\n        }\n    }\n\n    @Test\n    func `reports api errors`() async throws {\n        let env = try GeminiTestEnvironment()\n        defer { env.cleanup() }\n        try env.writeCredentials(\n            accessToken: \"token\",\n            refreshToken: nil,\n            expiry: Date().addingTimeInterval(3600),\n            idToken: nil)\n\n        let dataLoader = GeminiAPITestHelpers.dataLoader { request in\n            guard let url = request.url, let host = url.host else {\n                throw URLError(.badURL)\n            }\n            switch host {\n            case \"cloudresourcemanager.googleapis.com\":\n                return GeminiAPITestHelpers.response(\n                    url: url.absoluteString,\n                    status: 200,\n                    body: GeminiAPITestHelpers.jsonData([\"projects\": []]))\n            case \"cloudcode-pa.googleapis.com\":\n                if url.path != \"/v1internal:retrieveUserQuota\" {\n                    return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data())\n                }\n                return GeminiAPITestHelpers.response(url: url.absoluteString, status: 500, body: Data())\n            default:\n                return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data())\n            }\n        }\n\n        let probe = GeminiStatusProbe(timeout: 1, homeDirectory: env.homeURL.path, dataLoader: dataLoader)\n        await Self.expectError(.apiError(\"HTTP 500\")) {\n            _ = try await probe.fetch()\n        }\n    }\n\n    @Test\n    func `reports not logged in when access token missing`() async throws {\n        let env = try GeminiTestEnvironment()\n        defer { env.cleanup() }\n        try env.writeCredentials(\n            accessToken: \"\",\n            refreshToken: nil,\n            expiry: Date().addingTimeInterval(3600),\n            idToken: nil)\n\n        let probe = GeminiStatusProbe(timeout: 1, homeDirectory: env.homeURL.path)\n        await Self.expectError(.notLoggedIn) {\n            _ = try await probe.fetch()\n        }\n    }\n\n    @Test\n    func `reports not logged in on401`() async throws {\n        let env = try GeminiTestEnvironment()\n        defer { env.cleanup() }\n        try env.writeCredentials(\n            accessToken: \"token\",\n            refreshToken: nil,\n            expiry: Date().addingTimeInterval(3600),\n            idToken: nil)\n\n        let dataLoader = GeminiAPITestHelpers.dataLoader { request in\n            guard let url = request.url, let host = url.host else {\n                throw URLError(.badURL)\n            }\n            switch host {\n            case \"cloudresourcemanager.googleapis.com\":\n                return GeminiAPITestHelpers.response(\n                    url: url.absoluteString,\n                    status: 200,\n                    body: GeminiAPITestHelpers.jsonData([\"projects\": []]))\n            case \"cloudcode-pa.googleapis.com\":\n                if url.path != \"/v1internal:retrieveUserQuota\" {\n                    return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data())\n                }\n                return GeminiAPITestHelpers.response(url: url.absoluteString, status: 401, body: Data())\n            default:\n                return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data())\n            }\n        }\n\n        let probe = GeminiStatusProbe(timeout: 1, homeDirectory: env.homeURL.path, dataLoader: dataLoader)\n        await Self.expectError(.notLoggedIn) {\n            _ = try await probe.fetch()\n        }\n    }\n\n    @Test\n    func `reports parse errors for invalid payload`() async throws {\n        let env = try GeminiTestEnvironment()\n        defer { env.cleanup() }\n        try env.writeCredentials(\n            accessToken: \"token\",\n            refreshToken: nil,\n            expiry: Date().addingTimeInterval(3600),\n            idToken: nil)\n\n        let dataLoader = GeminiAPITestHelpers.dataLoader { request in\n            guard let url = request.url, let host = url.host else {\n                throw URLError(.badURL)\n            }\n            switch host {\n            case \"cloudresourcemanager.googleapis.com\":\n                return GeminiAPITestHelpers.response(\n                    url: url.absoluteString,\n                    status: 200,\n                    body: GeminiAPITestHelpers.jsonData([\"projects\": []]))\n            case \"cloudcode-pa.googleapis.com\":\n                if url.path != \"/v1internal:retrieveUserQuota\" {\n                    return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data())\n                }\n                return GeminiAPITestHelpers.response(\n                    url: url.absoluteString,\n                    status: 200,\n                    body: GeminiAPITestHelpers.jsonData([\"buckets\": []]))\n            default:\n                return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data())\n            }\n        }\n\n        let probe = GeminiStatusProbe(timeout: 1, homeDirectory: env.homeURL.path, dataLoader: dataLoader)\n        do {\n            _ = try await probe.fetch()\n            #expect(Bool(false))\n        } catch {\n            let cast = error as? GeminiStatusProbeError\n            #expect(cast?.errorDescription?.contains(\"Could not parse Gemini usage\") == true)\n        }\n    }\n\n    private static func expectError(\n        _ expected: GeminiStatusProbeError,\n        operation: () async throws -> Void) async\n    {\n        do {\n            try await operation()\n            #expect(Bool(false))\n        } catch {\n            #expect(error as? GeminiStatusProbeError == expected)\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/GeminiStatusProbePlanTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n\n@Suite(.serialized)\nstruct GeminiStatusProbePlanTests {\n    @Test\n    func `selects project id for quota requests`() async throws {\n        let env = try GeminiTestEnvironment()\n        defer { env.cleanup() }\n        try env.writeCredentials(\n            accessToken: \"token\",\n            refreshToken: nil,\n            expiry: Date().addingTimeInterval(3600),\n            idToken: nil)\n\n        let expectedProject = \"gen-lang-client-123\"\n        let dataLoader = GeminiAPITestHelpers.dataLoader { request in\n            guard let url = request.url, let host = url.host else {\n                throw URLError(.badURL)\n            }\n            switch host {\n            case \"cloudresourcemanager.googleapis.com\":\n                let json = GeminiAPITestHelpers.jsonData([\n                    \"projects\": [\n                        [\"projectId\": expectedProject],\n                    ],\n                ])\n                return GeminiAPITestHelpers.response(url: url.absoluteString, status: 200, body: json)\n            case \"cloudcode-pa.googleapis.com\":\n                if url.path == \"/v1internal:loadCodeAssist\" {\n                    return GeminiAPITestHelpers.response(\n                        url: url.absoluteString,\n                        status: 200,\n                        body: GeminiAPITestHelpers.loadCodeAssistFreeTierResponse())\n                }\n                if url.path != \"/v1internal:retrieveUserQuota\" {\n                    return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data())\n                }\n                let bodyText = request.httpBody.flatMap { String(data: $0, encoding: .utf8) } ?? \"\"\n                if !bodyText.contains(expectedProject) {\n                    return GeminiAPITestHelpers.response(url: url.absoluteString, status: 400, body: Data())\n                }\n                return GeminiAPITestHelpers.response(\n                    url: url.absoluteString,\n                    status: 200,\n                    body: GeminiAPITestHelpers.sampleFlashQuotaResponse())\n            default:\n                return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data())\n            }\n        }\n\n        let probe = GeminiStatusProbe(timeout: 1, homeDirectory: env.homeURL.path, dataLoader: dataLoader)\n        let snapshot = try await probe.fetch()\n        #expect(snapshot.modelQuotas.contains { $0.percentLeft == 40 })\n        let usage = snapshot.toUsageSnapshot()\n        #expect(usage.secondary?.remainingPercent == 40)\n        #expect(usage.tertiary == nil)\n    }\n\n    @Test\n    func `prefers load code assist project for quota requests`() async throws {\n        let env = try GeminiTestEnvironment()\n        defer { env.cleanup() }\n        try env.writeCredentials(\n            accessToken: \"token\",\n            refreshToken: nil,\n            expiry: Date().addingTimeInterval(3600),\n            idToken: nil)\n\n        let loadCodeAssistProject = \"cloudaicompanion-123\"\n        let fallbackProject = \"gen-lang-client-should-not-use\"\n        let dataLoader = GeminiAPITestHelpers.dataLoader { request in\n            guard let url = request.url, let host = url.host else {\n                throw URLError(.badURL)\n            }\n            switch host {\n            case \"cloudresourcemanager.googleapis.com\":\n                let json = GeminiAPITestHelpers.jsonData([\n                    \"projects\": [\n                        [\"projectId\": fallbackProject],\n                    ],\n                ])\n                return GeminiAPITestHelpers.response(url: url.absoluteString, status: 200, body: json)\n            case \"cloudcode-pa.googleapis.com\":\n                if url.path == \"/v1internal:loadCodeAssist\" {\n                    return GeminiAPITestHelpers.response(\n                        url: url.absoluteString,\n                        status: 200,\n                        body: GeminiAPITestHelpers.loadCodeAssistResponse(\n                            tierId: \"free-tier\",\n                            projectId: loadCodeAssistProject))\n                }\n                if url.path != \"/v1internal:retrieveUserQuota\" {\n                    return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data())\n                }\n                let bodyText = request.httpBody.flatMap { String(data: $0, encoding: .utf8) } ?? \"\"\n                if !bodyText.contains(loadCodeAssistProject) {\n                    return GeminiAPITestHelpers.response(url: url.absoluteString, status: 400, body: Data())\n                }\n                return GeminiAPITestHelpers.response(\n                    url: url.absoluteString,\n                    status: 200,\n                    body: GeminiAPITestHelpers.sampleFlashQuotaResponse())\n            default:\n                return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data())\n            }\n        }\n\n        let probe = GeminiStatusProbe(timeout: 1, homeDirectory: env.homeURL.path, dataLoader: dataLoader)\n        let snapshot = try await probe.fetch()\n        #expect(snapshot.modelQuotas.contains { $0.percentLeft == 40 })\n        let usage = snapshot.toUsageSnapshot()\n        #expect(usage.secondary?.remainingPercent == 40)\n        #expect(usage.tertiary == nil)\n    }\n\n    @Test\n    func `separates flash and flash lite quota buckets from api response`() async throws {\n        let env = try GeminiTestEnvironment()\n        defer { env.cleanup() }\n        try env.writeCredentials(\n            accessToken: \"token\",\n            refreshToken: nil,\n            expiry: Date().addingTimeInterval(3600),\n            idToken: nil)\n\n        let dataLoader = GeminiAPITestHelpers.dataLoader { request in\n            guard let url = request.url, let host = url.host else {\n                throw URLError(.badURL)\n            }\n            switch host {\n            case \"cloudresourcemanager.googleapis.com\":\n                return GeminiAPITestHelpers.response(\n                    url: url.absoluteString,\n                    status: 200,\n                    body: GeminiAPITestHelpers.jsonData([\"projects\": []]))\n            case \"cloudcode-pa.googleapis.com\":\n                if url.path == \"/v1internal:loadCodeAssist\" {\n                    return GeminiAPITestHelpers.response(\n                        url: url.absoluteString,\n                        status: 200,\n                        body: GeminiAPITestHelpers.loadCodeAssistStandardTierResponse())\n                }\n                if url.path != \"/v1internal:retrieveUserQuota\" {\n                    return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data())\n                }\n                return GeminiAPITestHelpers.response(\n                    url: url.absoluteString,\n                    status: 200,\n                    body: GeminiAPITestHelpers.sampleQuotaResponse())\n            default:\n                return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data())\n            }\n        }\n\n        let probe = GeminiStatusProbe(timeout: 1, homeDirectory: env.homeURL.path, dataLoader: dataLoader)\n        let snapshot = try await probe.fetch()\n        let usage = snapshot.toUsageSnapshot()\n\n        #expect(usage.primary?.remainingPercent == 60.0)\n        #expect(usage.secondary?.remainingPercent == 90.0)\n        #expect(usage.tertiary?.remainingPercent == 80.0)\n    }\n\n    @Test\n    func `detects paid from standard tier`() async throws {\n        let env = try GeminiTestEnvironment()\n        defer { env.cleanup() }\n        try env.writeCredentials(\n            accessToken: \"token\",\n            refreshToken: nil,\n            expiry: Date().addingTimeInterval(3600),\n            idToken: nil)\n\n        let dataLoader = GeminiAPITestHelpers.dataLoader { request in\n            guard let url = request.url, let host = url.host else {\n                throw URLError(.badURL)\n            }\n            switch host {\n            case \"cloudresourcemanager.googleapis.com\":\n                return GeminiAPITestHelpers.response(\n                    url: url.absoluteString,\n                    status: 200,\n                    body: GeminiAPITestHelpers.jsonData([\"projects\": []]))\n            case \"cloudcode-pa.googleapis.com\":\n                if url.path == \"/v1internal:loadCodeAssist\" {\n                    return GeminiAPITestHelpers.response(\n                        url: url.absoluteString,\n                        status: 200,\n                        body: GeminiAPITestHelpers.loadCodeAssistStandardTierResponse())\n                }\n                if url.path != \"/v1internal:retrieveUserQuota\" {\n                    return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data())\n                }\n                return GeminiAPITestHelpers.response(\n                    url: url.absoluteString,\n                    status: 200,\n                    body: GeminiAPITestHelpers.sampleQuotaResponse())\n            default:\n                return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data())\n            }\n        }\n\n        let probe = GeminiStatusProbe(timeout: 1, homeDirectory: env.homeURL.path, dataLoader: dataLoader)\n        let snapshot = try await probe.fetch()\n        #expect(snapshot.accountPlan == \"Paid\")\n    }\n\n    @Test\n    func `detects workspace from free tier with hosted domain`() async throws {\n        let env = try GeminiTestEnvironment()\n        defer { env.cleanup() }\n        let idToken = GeminiAPITestHelpers.makeIDToken(email: \"user@company.com\", hostedDomain: \"company.com\")\n        try env.writeCredentials(\n            accessToken: \"token\",\n            refreshToken: nil,\n            expiry: Date().addingTimeInterval(3600),\n            idToken: idToken)\n\n        let dataLoader = GeminiAPITestHelpers.dataLoader { request in\n            guard let url = request.url, let host = url.host else {\n                throw URLError(.badURL)\n            }\n            switch host {\n            case \"cloudresourcemanager.googleapis.com\":\n                return GeminiAPITestHelpers.response(\n                    url: url.absoluteString,\n                    status: 200,\n                    body: GeminiAPITestHelpers.jsonData([\"projects\": []]))\n            case \"cloudcode-pa.googleapis.com\":\n                if url.path == \"/v1internal:loadCodeAssist\" {\n                    return GeminiAPITestHelpers.response(\n                        url: url.absoluteString,\n                        status: 200,\n                        body: GeminiAPITestHelpers.loadCodeAssistFreeTierResponse())\n                }\n                if url.path != \"/v1internal:retrieveUserQuota\" {\n                    return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data())\n                }\n                return GeminiAPITestHelpers.response(\n                    url: url.absoluteString,\n                    status: 200,\n                    body: GeminiAPITestHelpers.sampleQuotaResponse())\n            default:\n                return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data())\n            }\n        }\n\n        let probe = GeminiStatusProbe(timeout: 1, homeDirectory: env.homeURL.path, dataLoader: dataLoader)\n        let snapshot = try await probe.fetch()\n        #expect(snapshot.accountPlan == \"Workspace\")\n    }\n\n    @Test\n    func `detects free from free tier without hosted domain`() async throws {\n        let env = try GeminiTestEnvironment()\n        defer { env.cleanup() }\n        let idToken = GeminiAPITestHelpers.makeIDToken(email: \"user@gmail.com\")\n        try env.writeCredentials(\n            accessToken: \"token\",\n            refreshToken: nil,\n            expiry: Date().addingTimeInterval(3600),\n            idToken: idToken)\n\n        let dataLoader = GeminiAPITestHelpers.dataLoader { request in\n            guard let url = request.url, let host = url.host else {\n                throw URLError(.badURL)\n            }\n            switch host {\n            case \"cloudresourcemanager.googleapis.com\":\n                return GeminiAPITestHelpers.response(\n                    url: url.absoluteString,\n                    status: 200,\n                    body: GeminiAPITestHelpers.jsonData([\"projects\": []]))\n            case \"cloudcode-pa.googleapis.com\":\n                if url.path == \"/v1internal:loadCodeAssist\" {\n                    return GeminiAPITestHelpers.response(\n                        url: url.absoluteString,\n                        status: 200,\n                        body: GeminiAPITestHelpers.loadCodeAssistFreeTierResponse())\n                }\n                if url.path != \"/v1internal:retrieveUserQuota\" {\n                    return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data())\n                }\n                return GeminiAPITestHelpers.response(\n                    url: url.absoluteString,\n                    status: 200,\n                    body: GeminiAPITestHelpers.sampleFlashQuotaResponse())\n            default:\n                return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data())\n            }\n        }\n\n        let probe = GeminiStatusProbe(timeout: 1, homeDirectory: env.homeURL.path, dataLoader: dataLoader)\n        let snapshot = try await probe.fetch()\n        #expect(snapshot.accountPlan == \"Free\")\n    }\n\n    @Test\n    func `detects legacy from legacy tier`() async throws {\n        let env = try GeminiTestEnvironment()\n        defer { env.cleanup() }\n        try env.writeCredentials(\n            accessToken: \"token\",\n            refreshToken: nil,\n            expiry: Date().addingTimeInterval(3600),\n            idToken: nil)\n\n        let dataLoader = GeminiAPITestHelpers.dataLoader { request in\n            guard let url = request.url, let host = url.host else {\n                throw URLError(.badURL)\n            }\n            switch host {\n            case \"cloudresourcemanager.googleapis.com\":\n                return GeminiAPITestHelpers.response(\n                    url: url.absoluteString,\n                    status: 200,\n                    body: GeminiAPITestHelpers.jsonData([\"projects\": []]))\n            case \"cloudcode-pa.googleapis.com\":\n                if url.path == \"/v1internal:loadCodeAssist\" {\n                    return GeminiAPITestHelpers.response(\n                        url: url.absoluteString,\n                        status: 200,\n                        body: GeminiAPITestHelpers.loadCodeAssistLegacyTierResponse())\n                }\n                if url.path != \"/v1internal:retrieveUserQuota\" {\n                    return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data())\n                }\n                return GeminiAPITestHelpers.response(\n                    url: url.absoluteString,\n                    status: 200,\n                    body: GeminiAPITestHelpers.sampleQuotaResponse())\n            default:\n                return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data())\n            }\n        }\n\n        let probe = GeminiStatusProbe(timeout: 1, homeDirectory: env.homeURL.path, dataLoader: dataLoader)\n        let snapshot = try await probe.fetch()\n        #expect(snapshot.accountPlan == \"Legacy\")\n    }\n\n    @Test\n    func `leaves blank when load code assist fails`() async throws {\n        let env = try GeminiTestEnvironment()\n        defer { env.cleanup() }\n        try env.writeCredentials(\n            accessToken: \"token\",\n            refreshToken: nil,\n            expiry: Date().addingTimeInterval(3600),\n            idToken: nil)\n\n        let dataLoader = GeminiAPITestHelpers.dataLoader { request in\n            guard let url = request.url, let host = url.host else {\n                throw URLError(.badURL)\n            }\n            switch host {\n            case \"cloudresourcemanager.googleapis.com\":\n                return GeminiAPITestHelpers.response(\n                    url: url.absoluteString,\n                    status: 200,\n                    body: GeminiAPITestHelpers.jsonData([\"projects\": []]))\n            case \"cloudcode-pa.googleapis.com\":\n                if url.path == \"/v1internal:loadCodeAssist\" {\n                    return GeminiAPITestHelpers.response(\n                        url: url.absoluteString,\n                        status: 500,\n                        body: Data())\n                }\n                if url.path != \"/v1internal:retrieveUserQuota\" {\n                    return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data())\n                }\n                return GeminiAPITestHelpers.response(\n                    url: url.absoluteString,\n                    status: 200,\n                    body: GeminiAPITestHelpers.sampleQuotaResponse())\n            default:\n                return GeminiAPITestHelpers.response(url: url.absoluteString, status: 404, body: Data())\n            }\n        }\n\n        let probe = GeminiStatusProbe(timeout: 1, homeDirectory: env.homeURL.path, dataLoader: dataLoader)\n        let snapshot = try await probe.fetch()\n        #expect(snapshot.accountPlan == nil)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/GeminiStatusProbeTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n\nstruct GeminiStatusProbeTests {\n    /// Sample /stats output from Gemini CLI (actual format with box-drawing chars)\n    static let sampleStatsOutput = \"\"\"\n    │  Model Usage                            Reqs                  Usage left  │\n    │  ───────────────────────────────────────────────────────────────────────  │\n    │  gemini-2.5-flash                          -   99.8% (Resets in 20h 37m)  │\n    │  gemini-2.5-flash-lite                     -   99.8% (Resets in 20h 37m)  │\n    │  gemini-2.5-pro                            -      100.0% (Resets in 24h)  │\n    │  gemini-3-pro-preview                      -      100.0% (Resets in 24h)  │\n    \"\"\"\n\n    // MARK: - Legacy CLI parsing tests (kept for fallback support)\n\n    @Test\n    func `parses minimum percent from multiple models`() throws {\n        let snap = try GeminiStatusProbe.parse(text: Self.sampleStatsOutput)\n        #expect(snap.dailyPercentLeft == 99.8)\n        #expect(snap.resetDescription == \"Resets in 20h 37m\")\n    }\n\n    @Test\n    func `parses lower percent correctly`() throws {\n        let output = \"\"\"\n        │  Model Usage                                                  Reqs                  Usage left  │\n        │  gemini-2.5-flash                                               10       85.5% (Resets in 12h)  │\n        │  gemini-2.5-pro                                                  5       92.0% (Resets in 12h)  │\n        \"\"\"\n        let snap = try GeminiStatusProbe.parse(text: output)\n        #expect(snap.dailyPercentLeft == 85.5)\n        #expect(snap.resetDescription == \"Resets in 12h\")\n    }\n\n    @Test\n    func `handles zero percent usage`() throws {\n        let output = \"\"\"\n        │  gemini-2.5-flash                                               50        0.0% (Resets in 6h)  │\n        │  gemini-2.5-pro                                                 20       15.0% (Resets in 6h)  │\n        \"\"\"\n        let snap = try GeminiStatusProbe.parse(text: output)\n        #expect(snap.dailyPercentLeft == 0.0)\n        #expect(snap.resetDescription == \"Resets in 6h\")\n    }\n\n    @Test\n    func `handles100 percent remaining`() throws {\n        let output = \"\"\"\n        │  gemini-2.5-flash                                                -      100.0% (Resets in 24h)  │\n        \"\"\"\n        let snap = try GeminiStatusProbe.parse(text: output)\n        #expect(snap.dailyPercentLeft == 100.0)\n    }\n\n    @Test\n    func `throws on empty output`() {\n        #expect(throws: GeminiStatusProbeError.self) {\n            try GeminiStatusProbe.parse(text: \"\")\n        }\n    }\n\n    @Test\n    func `throws on no usage data`() {\n        let output = \"\"\"\n        Welcome to Gemini CLI!\n        Type /help for available commands.\n        \"\"\"\n        #expect(throws: GeminiStatusProbeError.self) {\n            try GeminiStatusProbe.parse(text: output)\n        }\n    }\n\n    @Test\n    func `strips ANSI codes before parsing`() throws {\n        let output =\n            \"\\u{1B}[32m│\\u{1B}[0m  gemini-2.5-flash                                                -       75.5% \" +\n            \"(Resets in 18h)  │\"\n        let snap = try GeminiStatusProbe.parse(text: output)\n        #expect(snap.dailyPercentLeft == 75.5)\n    }\n\n    @Test\n    func `preserves raw text`() throws {\n        let snap = try GeminiStatusProbe.parse(text: Self.sampleStatsOutput)\n        #expect(snap.rawText == Self.sampleStatsOutput)\n        #expect(snap.accountEmail == nil) // Legacy parse doesn't extract email\n        #expect(snap.accountPlan == nil)\n    }\n\n    @Test\n    func `parses various reset descriptions`() throws {\n        let cases: [(String, String)] = [\n            (\"Resets in 24h\", \"Resets in 24h\"),\n            (\"Resets in 1h 30m\", \"Resets in 1h 30m\"),\n            (\"Resets tomorrow\", \"Resets tomorrow\"),\n        ]\n        for (resetStr, expected) in cases {\n            let output = \"\"\"\n            │  gemini-2.5-flash                                                -       50.0% (\\(resetStr))  │\n            \"\"\"\n            let snap = try GeminiStatusProbe.parse(text: output)\n            #expect(snap.resetDescription == expected)\n        }\n    }\n\n    @Test\n    func `throws not logged in on auth prompt`() {\n        let authOutputs = [\n            \"Waiting for auth... (Press ESC or CTRL+C to cancel)\",\n            \"Login with Google\\nUse Gemini API key\",\n            \"Some preamble\\nWaiting for auth\\nMore text\",\n        ]\n        for output in authOutputs {\n            #expect(throws: GeminiStatusProbeError.notLoggedIn) {\n                try GeminiStatusProbe.parse(text: output)\n            }\n        }\n    }\n\n    // MARK: - Model quota grouping tests\n\n    @Test\n    func `parses models into quota array`() throws {\n        let snap = try GeminiStatusProbe.parse(text: Self.sampleStatsOutput)\n        // Should parse multiple models (exact count may change as Google adds/removes models)\n        #expect(snap.modelQuotas.count >= 2)\n\n        // All model IDs should start with \"gemini\"\n        for quota in snap.modelQuotas {\n            #expect(quota.modelId.hasPrefix(\"gemini\"))\n        }\n    }\n\n    @Test\n    func `lowest percent left returns minimum`() throws {\n        let snap = try GeminiStatusProbe.parse(text: Self.sampleStatsOutput)\n        // Flash models are 99.8%, Pro models are 100%, so min should be 99.8\n        #expect(snap.lowestPercentLeft == 99.8)\n    }\n\n    @Test\n    func `tier grouping by keyword`() throws {\n        // Test that flash/pro keyword filtering works (model names may change)\n        let snap = try GeminiStatusProbe.parse(text: Self.sampleStatsOutput)\n\n        let flashQuotas = snap.modelQuotas.filter { $0.modelId.contains(\"flash\") && !$0.modelId.contains(\"flash-lite\") }\n        let flashLiteQuotas = snap.modelQuotas.filter { $0.modelId.contains(\"flash-lite\") }\n        let proQuotas = snap.modelQuotas.filter { $0.modelId.contains(\"pro\") }\n\n        // Should have at least one of each tier in sample\n        #expect(!flashQuotas.isEmpty)\n        #expect(!flashLiteQuotas.isEmpty)\n        #expect(!proQuotas.isEmpty)\n    }\n\n    @Test\n    func `tier minimum calculation`() throws {\n        // Use controlled test data to verify min-per-tier logic\n        // Model names must start with \"gemini-\" to match the parser regex\n        let output = \"\"\"\n        │  gemini-a-flash                            10       85.0% (Resets in 24h)  │\n        │  gemini-b-flash                             5       92.0% (Resets in 24h)  │\n        │  gemini-c-pro                               2       95.0% (Resets in 24h)  │\n        │  gemini-d-pro                               1       99.0% (Resets in 24h)  │\n        \"\"\"\n        let snap = try GeminiStatusProbe.parse(text: output)\n\n        let flashQuotas = snap.modelQuotas.filter { $0.modelId.contains(\"flash\") }\n        let proQuotas = snap.modelQuotas.filter { $0.modelId.contains(\"pro\") }\n\n        let flashMin = flashQuotas.min(by: { $0.percentLeft < $1.percentLeft })\n        let proMin = proQuotas.min(by: { $0.percentLeft < $1.percentLeft })\n\n        #expect(flashMin?.percentLeft == 85.0)\n        #expect(proMin?.percentLeft == 95.0)\n    }\n\n    @Test\n    func `quotas have reset descriptions`() throws {\n        let snap = try GeminiStatusProbe.parse(text: Self.sampleStatsOutput)\n\n        // At least some quotas should have reset descriptions\n        let hasResets = snap.modelQuotas.contains { $0.resetDescription != nil }\n        #expect(hasResets)\n    }\n\n    @Test\n    func `to usage snapshot creates separate flash and flash lite meters`() throws {\n        let snap = try GeminiStatusProbe.parse(text: Self.sampleStatsOutput)\n        let usage = snap.toUsageSnapshot()\n\n        #expect(usage.primary?.remainingPercent == 100.0)\n        #expect(usage.secondary?.remainingPercent == 99.8)\n        #expect(usage.tertiary?.remainingPercent == 99.8)\n        #expect(usage.primary?.windowMinutes == 1440)\n        #expect(usage.secondary?.windowMinutes == 1440)\n        #expect(usage.tertiary?.windowMinutes == 1440)\n        #expect(usage.secondary?.resetDescription == \"Resets in 20h 37m\")\n        #expect(usage.tertiary?.resetDescription == \"Resets in 20h 37m\")\n    }\n\n    @Test\n    func `to usage snapshot does not let flash lite contaminate flash bucket`() throws {\n        let output = \"\"\"\n        │  gemini-2.5-flash                           10       91.0% (Resets in 12h)  │\n        │  gemini-2.5-flash-lite                       5       33.0% (Resets in 6h)   │\n        │  gemini-2.5-pro                              2       80.0% (Resets in 24h)  │\n        \"\"\"\n        let snap = try GeminiStatusProbe.parse(text: output)\n        let usage = snap.toUsageSnapshot()\n\n        #expect(usage.secondary?.remainingPercent == 91.0)\n        #expect(usage.tertiary?.remainingPercent == 33.0)\n        #expect(usage.secondary?.resetDescription == \"Resets in 12h\")\n        #expect(usage.tertiary?.resetDescription == \"Resets in 6h\")\n    }\n\n    @Test\n    func `to usage snapshot omits tertiary when no flash lite exists`() throws {\n        let output = \"\"\"\n        │  gemini-2.5-flash                           10       85.0% (Resets in 12h)  │\n        │  gemini-2.5-pro                              2       95.0% (Resets in 24h)  │\n        \"\"\"\n        let snap = try GeminiStatusProbe.parse(text: output)\n        let usage = snap.toUsageSnapshot()\n\n        #expect(usage.secondary?.remainingPercent == 85.0)\n        #expect(usage.tertiary == nil)\n    }\n\n    @Test\n    func `to usage snapshot uses lowest remaining quota per tier`() throws {\n        let output = \"\"\"\n        │  gemini-a-flash                            10       91.0% (Resets in 12h)  │\n        │  gemini-b-flash                             5       74.0% (Resets in 10h)  │\n        │  gemini-c-flash-lite                        8       66.0% (Resets in 9h)   │\n        │  gemini-d-flash-lite                        1       42.0% (Resets in 7h)   │\n        │  gemini-e-pro                               2       88.0% (Resets in 24h)  │\n        │  gemini-f-pro                               1       97.0% (Resets in 25h)  │\n        \"\"\"\n        let snap = try GeminiStatusProbe.parse(text: output)\n        let usage = snap.toUsageSnapshot()\n\n        #expect(usage.primary?.remainingPercent == 88.0)\n        #expect(usage.secondary?.remainingPercent == 74.0)\n        #expect(usage.tertiary?.remainingPercent == 42.0)\n        #expect(usage.secondary?.resetDescription == \"Resets in 10h\")\n        #expect(usage.tertiary?.resetDescription == \"Resets in 7h\")\n    }\n\n    // MARK: - Live API test\n\n    @Test\n    func `live gemini fetch`() async throws {\n        guard ProcessInfo.processInfo.environment[\"LIVE_GEMINI_FETCH\"] == \"1\" else {\n            return\n        }\n        let probe = GeminiStatusProbe()\n        let snap = try await probe.fetch()\n        print(\n            \"\"\"\n            Live Gemini usage (via API):\n            models: \\(snap.modelQuotas.map { \"\\($0.modelId): \\($0.percentLeft)%\" }.joined(separator: \", \"))\n            lowest: \\(snap.lowestPercentLeft ?? -1)% left\n            \"\"\")\n        #expect(!snap.modelQuotas.isEmpty)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/GeminiTestEnvironment.swift",
    "content": "import Foundation\n\nstruct GeminiTestEnvironment {\n    enum GeminiCLILayout {\n        case npmNested\n        case nixShare\n    }\n\n    let homeURL: URL\n    private let geminiDir: URL\n\n    init() throws {\n        let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)\n        try FileManager.default.createDirectory(at: root, withIntermediateDirectories: true)\n        let geminiDir = root.appendingPathComponent(\".gemini\")\n        try FileManager.default.createDirectory(at: geminiDir, withIntermediateDirectories: true)\n        self.homeURL = root\n        self.geminiDir = geminiDir\n    }\n\n    func cleanup() {\n        try? FileManager.default.removeItem(at: self.homeURL)\n    }\n\n    func writeSettings(authType: String) throws {\n        let payload: [String: Any] = [\n            \"security\": [\n                \"auth\": [\n                    \"selectedType\": authType,\n                ],\n            ],\n        ]\n        let data = try JSONSerialization.data(withJSONObject: payload)\n        try data.write(to: self.geminiDir.appendingPathComponent(\"settings.json\"), options: .atomic)\n    }\n\n    func writeCredentials(accessToken: String, refreshToken: String?, expiry: Date, idToken: String?) throws {\n        var payload: [String: Any] = [\n            \"access_token\": accessToken,\n            \"expiry_date\": expiry.timeIntervalSince1970 * 1000,\n        ]\n        if let refreshToken { payload[\"refresh_token\"] = refreshToken }\n        if let idToken { payload[\"id_token\"] = idToken }\n        let data = try JSONSerialization.data(withJSONObject: payload)\n        try data.write(to: self.geminiDir.appendingPathComponent(\"oauth_creds.json\"), options: .atomic)\n    }\n\n    func readCredentials() throws -> [String: Any] {\n        let url = self.geminiDir.appendingPathComponent(\"oauth_creds.json\")\n        let data = try Data(contentsOf: url)\n        let object = try JSONSerialization.jsonObject(with: data)\n        return object as? [String: Any] ?? [:]\n    }\n\n    func writeFakeGeminiCLI(includeOAuth: Bool = true, layout: GeminiCLILayout = .npmNested) throws -> URL {\n        let base = self.homeURL.appendingPathComponent(\"gemini-cli\")\n        let binDir = base.appendingPathComponent(\"bin\")\n        try FileManager.default.createDirectory(at: binDir, withIntermediateDirectories: true)\n\n        let oauthPath: URL = switch layout {\n        case .npmNested:\n            base\n                .appendingPathComponent(\"lib\")\n                .appendingPathComponent(\"node_modules\")\n                .appendingPathComponent(\"@google\")\n                .appendingPathComponent(\"gemini-cli\")\n                .appendingPathComponent(\"node_modules\")\n                .appendingPathComponent(\"@google\")\n                .appendingPathComponent(\"gemini-cli-core\")\n                .appendingPathComponent(\"dist\")\n                .appendingPathComponent(\"src\")\n                .appendingPathComponent(\"code_assist\")\n                .appendingPathComponent(\"oauth2.js\")\n        case .nixShare:\n            base\n                .appendingPathComponent(\"share\")\n                .appendingPathComponent(\"gemini-cli\")\n                .appendingPathComponent(\"node_modules\")\n                .appendingPathComponent(\"@google\")\n                .appendingPathComponent(\"gemini-cli-core\")\n                .appendingPathComponent(\"dist\")\n                .appendingPathComponent(\"src\")\n                .appendingPathComponent(\"code_assist\")\n                .appendingPathComponent(\"oauth2.js\")\n        }\n\n        if includeOAuth {\n            try FileManager.default.createDirectory(\n                at: oauthPath.deletingLastPathComponent(),\n                withIntermediateDirectories: true)\n\n            let oauthContent = \"\"\"\n            const OAUTH_CLIENT_ID = 'test-client-id';\n            const OAUTH_CLIENT_SECRET = 'test-client-secret';\n            \"\"\"\n            try oauthContent.write(to: oauthPath, atomically: true, encoding: .utf8)\n        }\n\n        let geminiBinary = binDir.appendingPathComponent(\"gemini\")\n        try \"#!/bin/bash\\nexit 0\\n\".write(to: geminiBinary, atomically: true, encoding: .utf8)\n        try FileManager.default.setAttributes(\n            [.posixPermissions: 0o755],\n            ofItemAtPath: geminiBinary.path)\n        return geminiBinary\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/GoogleWorkspaceStatusTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBar\n\n@MainActor\nstruct GoogleWorkspaceStatusTests {\n    private let productID = \"npdyhgECDJ6tB66MxXyo\"\n\n    @Test\n    func `parse workspace status selects worst incident`() throws {\n        let data = Data(#\"\"\"\n        [\n          {\n            \"id\": \"inc-1\",\n            \"begin\": \"2025-12-02T09:00:00+00:00\",\n            \"end\": null,\n            \"affected_products\": [\n              {\"title\": \"Gemini\", \"id\": \"npdyhgECDJ6tB66MxXyo\"}\n            ],\n            \"most_recent_update\": {\n              \"when\": \"2025-12-02T10:00:00+00:00\",\n              \"status\": \"SERVICE_INFORMATION\",\n              \"text\": \"**Summary**\\nMinor issue.\\n\"\n            }\n          },\n          {\n            \"id\": \"inc-2\",\n            \"begin\": \"2025-12-02T11:00:00+00:00\",\n            \"end\": null,\n            \"affected_products\": [\n              {\"title\": \"Gemini\", \"id\": \"npdyhgECDJ6tB66MxXyo\"}\n            ],\n            \"most_recent_update\": {\n              \"when\": \"2025-12-02T12:00:00+00:00\",\n              \"status\": \"SERVICE_OUTAGE\",\n              \"text\": \"**Summary**\\nGemini API error.\\n\"\n            }\n          }\n        ]\n        \"\"\"#.utf8)\n\n        let status = try UsageStore.parseGoogleWorkspaceStatus(data: data, productID: self.productID)\n        #expect(status.indicator == .critical)\n        #expect(status.description == \"Gemini API error.\")\n        #expect(status.updatedAt != nil)\n    }\n\n    @Test\n    func `parse workspace status ignores resolved incidents`() throws {\n        let data = Data(#\"\"\"\n        [\n          {\n            \"id\": \"inc-3\",\n            \"begin\": \"2025-12-02T08:00:00+00:00\",\n            \"end\": \"2025-12-02T09:00:00+00:00\",\n            \"affected_products\": [\n              {\"title\": \"Gemini\", \"id\": \"npdyhgECDJ6tB66MxXyo\"}\n            ],\n            \"most_recent_update\": {\n              \"when\": \"2025-12-02T09:00:00+00:00\",\n              \"status\": \"AVAILABLE\",\n              \"text\": \"**Summary**\\nResolved.\\n\"\n            }\n          }\n        ]\n        \"\"\"#.utf8)\n\n        let status = try UsageStore.parseGoogleWorkspaceStatus(data: data, productID: self.productID)\n        #expect(status.indicator == .none)\n        #expect(status.description == nil)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/HistoricalUsagePaceTestSupport.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n@testable import CodexBar\n\nextension HistoricalUsagePaceTests {\n    private static let dashboardTimeZone: TimeZone = .current\n\n    static func linearCurve(end: Double) -> [Double] {\n        let clampedEnd = max(0, min(100, end))\n        let count = CodexHistoricalDataset.gridPointCount\n        return (0..<count).map { index in\n            let u = Double(index) / Double(count - 1)\n            return clampedEnd * u\n        }\n    }\n\n    static func outlierCurve() -> [Double] {\n        let count = CodexHistoricalDataset.gridPointCount\n        return (0..<count).map { index in\n            let u = Double(index) / Double(count - 1)\n            return min(100, 80 + (20 * u))\n        }\n    }\n\n    static func makeTempURL() -> URL {\n        let root = FileManager.default.temporaryDirectory\n            .appendingPathComponent(\"codexbar-historical-tests\", isDirectory: true)\n            .appendingPathComponent(UUID().uuidString, isDirectory: true)\n        return root.appendingPathComponent(\"usage-history.jsonl\", isDirectory: false)\n    }\n\n    static func syntheticBreakdown(\n        endingAt endDate: Date,\n        days: Int,\n        dailyCredits: Double,\n        overridesByDayOffset: [Int: Double] = [:]) -> [OpenAIDashboardDailyBreakdown]\n    {\n        var calendar = Calendar(identifier: .gregorian)\n        calendar.timeZone = Self.dashboardTimeZone\n        let formatter = DateFormatter()\n        formatter.locale = Locale(identifier: \"en_US_POSIX\")\n        formatter.timeZone = Self.dashboardTimeZone\n        formatter.dateFormat = \"yyyy-MM-dd\"\n\n        let endDay = calendar.startOfDay(for: endDate)\n        return (0..<days).compactMap { offset in\n            guard let date = calendar.date(byAdding: .day, value: -offset, to: endDay) else { return nil }\n            let day = formatter.string(from: date)\n            let credits = overridesByDayOffset[offset] ?? dailyCredits\n            return OpenAIDashboardDailyBreakdown(\n                day: day,\n                services: [OpenAIDashboardServiceUsage(service: \"CLI\", creditsUsed: credits)],\n                totalCreditsUsed: credits)\n        }\n    }\n\n    static func gregorianDate(year: Int, month: Int, day: Int, hour: Int) -> Date {\n        var calendar = Calendar(identifier: .gregorian)\n        calendar.timeZone = Self.dashboardTimeZone\n        var components = DateComponents()\n        components.calendar = calendar\n        components.timeZone = Self.dashboardTimeZone\n        components.year = year\n        components.month = month\n        components.day = day\n        components.hour = hour\n        components.minute = 0\n        components.second = 0\n        guard let date = calendar.date(from: components) else {\n            preconditionFailure(\"Invalid Gregorian date components\")\n        }\n        return date\n    }\n\n    static func dayStart(for key: String) -> Date? {\n        let components = key.split(separator: \"-\", omittingEmptySubsequences: true)\n        guard components.count == 3,\n              let year = Int(components[0]),\n              let month = Int(components[1]),\n              let day = Int(components[2])\n        else {\n            return nil\n        }\n        var calendar = Calendar(identifier: .gregorian)\n        calendar.timeZone = Self.dashboardTimeZone\n        var dateComponents = DateComponents()\n        dateComponents.calendar = calendar\n        dateComponents.timeZone = Self.dashboardTimeZone\n        dateComponents.year = year\n        dateComponents.month = month\n        dateComponents.day = day\n        return calendar.date(from: dateComponents)\n    }\n\n    static func normalizeReset(_ value: Date) -> Date {\n        let bucket = 60.0\n        let rounded = (value.timeIntervalSinceReferenceDate / bucket).rounded() * bucket\n        return Date(timeIntervalSinceReferenceDate: rounded)\n    }\n\n    static func readHistoricalRecords(from fileURL: URL) throws -> [HistoricalUsageRecord] {\n        let data = try Data(contentsOf: fileURL)\n        let text = String(data: data, encoding: .utf8) ?? \"\"\n        let decoder = JSONDecoder()\n        decoder.dateDecodingStrategy = .iso8601\n        return text\n            .split(separator: \"\\n\", omittingEmptySubsequences: true)\n            .compactMap { line in\n                try? decoder.decode(HistoricalUsageRecord.self, from: Data(line.utf8))\n            }\n    }\n\n    static func recordDedupKeyCount(_ records: [HistoricalUsageRecord]) -> Int {\n        struct Key: Hashable {\n            let resetsAt: Date\n            let sampledAt: Date\n            let windowMinutes: Int\n            let accountKey: String?\n        }\n        let keys = records.map { record in\n            Key(\n                resetsAt: record.resetsAt,\n                sampledAt: record.sampledAt,\n                windowMinutes: record.windowMinutes,\n                accountKey: record.accountKey)\n        }\n        return Set(keys).count\n    }\n\n    static func datasetCurveSignature(_ dataset: CodexHistoricalDataset?) -> String {\n        guard let dataset else { return \"nil\" }\n        return dataset.weeks\n            .sorted { lhs, rhs in lhs.resetsAt < rhs.resetsAt }\n            .map { week in\n                let curve = week.curve.map { String(format: \"%.4f\", $0) }.joined(separator: \",\")\n                return \"\\(week.resetsAt.timeIntervalSinceReferenceDate)|\\(week.windowMinutes)|\\(curve)\"\n            }\n            .joined(separator: \"||\")\n    }\n\n    @MainActor\n    static func makeUsageStoreForBackfillTests(suite: String, historyFileURL: URL) throws -> UsageStore {\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let settings = SettingsStore(\n            userDefaults: defaults,\n            configStore: testConfigStore(suiteName: suite),\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore(),\n            codexCookieStore: InMemoryCookieHeaderStore(),\n            claudeCookieStore: InMemoryCookieHeaderStore(),\n            cursorCookieStore: InMemoryCookieHeaderStore(),\n            opencodeCookieStore: InMemoryCookieHeaderStore(),\n            factoryCookieStore: InMemoryCookieHeaderStore(),\n            minimaxCookieStore: InMemoryMiniMaxCookieStore(),\n            minimaxAPITokenStore: InMemoryMiniMaxAPITokenStore(),\n            kimiTokenStore: InMemoryKimiTokenStore(),\n            kimiK2TokenStore: InMemoryKimiK2TokenStore(),\n            augmentCookieStore: InMemoryCookieHeaderStore(),\n            ampCookieStore: InMemoryCookieHeaderStore(),\n            copilotTokenStore: InMemoryCopilotTokenStore(),\n            tokenAccountStore: InMemoryTokenAccountStore())\n        settings.historicalTrackingEnabled = true\n        return UsageStore(\n            fetcher: UsageFetcher(environment: [:]),\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            settings: settings,\n            historicalUsageHistoryStore: HistoricalUsageHistoryStore(fileURL: historyFileURL))\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/HistoricalUsagePaceTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n@testable import CodexBar\n\nstruct HistoricalUsagePaceTests {\n    @Test\n    func `history store reconstructs deterministic monotone curve`() async throws {\n        let fileURL = Self.makeTempURL()\n        let store = HistoricalUsageHistoryStore(fileURL: fileURL)\n        let windowMinutes = 10080\n        let duration = TimeInterval(windowMinutes) * 60\n        let resetsAt = Date().addingTimeInterval(-24 * 60 * 60)\n        let windowStart = resetsAt.addingTimeInterval(-duration)\n\n        let samples: [(u: Double, used: Double)] = [\n            (0.02, 3),\n            (0.10, 10),\n            (0.40, 40),\n            (0.60, 60),\n            (0.80, 80),\n            (0.98, 95),\n        ]\n        for sample in samples {\n            let sampledAt = windowStart.addingTimeInterval(sample.u * duration)\n            _ = await store.recordCodexWeekly(\n                window: RateWindow(\n                    usedPercent: sample.used,\n                    windowMinutes: windowMinutes,\n                    resetsAt: resetsAt,\n                    resetDescription: nil),\n                sampledAt: sampledAt,\n                accountKey: nil)\n        }\n\n        let dataset = await store.loadCodexDataset(accountKey: nil)\n        #expect(dataset?.weeks.count == 1)\n        let curve = try #require(dataset?.weeks.first?.curve)\n        #expect(curve.count == CodexHistoricalDataset.gridPointCount)\n        #expect(abs(curve[0]) < 0.001)\n        for index in 1..<curve.count {\n            #expect(curve[index] >= curve[index - 1])\n        }\n        // u=0.5 is index 84 in a 169-point grid.\n        #expect(abs(curve[84] - 50) < 0.5)\n    }\n\n    @Test\n    func `reconstruct week curve anchors at zero at window start`() async throws {\n        let fileURL = Self.makeTempURL()\n        let store = HistoricalUsageHistoryStore(fileURL: fileURL)\n        let windowMinutes = 10080\n        let duration = TimeInterval(windowMinutes) * 60\n        let resetsAt = Date().addingTimeInterval(-24 * 60 * 60)\n        let windowStart = resetsAt.addingTimeInterval(-duration)\n\n        let samples: [(u: Double, used: Double)] = [\n            (0.10, 12),\n            (0.25, 25),\n            (0.50, 50),\n            (0.70, 70),\n            (0.90, 90),\n            (0.98, 96),\n        ]\n        for sample in samples {\n            _ = await store.recordCodexWeekly(\n                window: RateWindow(\n                    usedPercent: sample.used,\n                    windowMinutes: windowMinutes,\n                    resetsAt: resetsAt,\n                    resetDescription: nil),\n                sampledAt: windowStart.addingTimeInterval(sample.u * duration),\n                accountKey: nil)\n        }\n\n        let curve = try #require(await store.loadCodexDataset(accountKey: nil)?.weeks.first?.curve)\n        #expect(abs(curve[0]) < 0.001)\n        #expect(curve[1] >= curve[0])\n    }\n\n    @Test\n    func `reconstruct week curve adds end anchor without breaking monotonicity`() async throws {\n        let fileURL = Self.makeTempURL()\n        let store = HistoricalUsageHistoryStore(fileURL: fileURL)\n        let windowMinutes = 10080\n        let duration = TimeInterval(windowMinutes) * 60\n        let resetsAt = Date().addingTimeInterval(-24 * 60 * 60)\n        let windowStart = resetsAt.addingTimeInterval(-duration)\n\n        let samples: [(u: Double, used: Double)] = [\n            (0.02, 2),\n            (0.15, 18),\n            (0.35, 40),\n            (0.55, 58),\n            (0.80, 82),\n            (0.90, 88),\n        ]\n        for sample in samples {\n            _ = await store.recordCodexWeekly(\n                window: RateWindow(\n                    usedPercent: sample.used,\n                    windowMinutes: windowMinutes,\n                    resetsAt: resetsAt,\n                    resetDescription: nil),\n                sampledAt: windowStart.addingTimeInterval(sample.u * duration),\n                accountKey: nil)\n        }\n\n        let curve = try #require(await store.loadCodexDataset(accountKey: nil)?.weeks.first?.curve)\n        for index in 1..<curve.count {\n            #expect(curve[index] >= curve[index - 1])\n        }\n        let last = try #require(curve.last)\n        #expect(abs(last - 88) < 1.5)\n    }\n\n    @Test\n    func `history store requires start and end coverage for complete week`() async {\n        let fileURL = Self.makeTempURL()\n        let store = HistoricalUsageHistoryStore(fileURL: fileURL)\n        let windowMinutes = 10080\n        let duration = TimeInterval(windowMinutes) * 60\n        let resetsAt = Date().addingTimeInterval(-24 * 60 * 60)\n        let windowStart = resetsAt.addingTimeInterval(-duration)\n\n        // Missing first-24h coverage on purpose.\n        let lateOnlySamples: [(u: Double, used: Double)] = [\n            (0.30, 20),\n            (0.45, 35),\n            (0.60, 50),\n            (0.75, 65),\n            (0.90, 80),\n            (0.98, 92),\n        ]\n        for sample in lateOnlySamples {\n            let sampledAt = windowStart.addingTimeInterval(sample.u * duration)\n            _ = await store.recordCodexWeekly(\n                window: RateWindow(\n                    usedPercent: sample.used,\n                    windowMinutes: windowMinutes,\n                    resetsAt: resetsAt,\n                    resetDescription: nil),\n                sampledAt: sampledAt,\n                accountKey: nil)\n        }\n\n        #expect(await store.loadCodexDataset(accountKey: nil) == nil)\n    }\n\n    @Test\n    func `evaluator applies smoothed probability and hides risk below threshold`() throws {\n        let now = Date(timeIntervalSince1970: 0)\n        let windowMinutes = 10080\n        let duration = TimeInterval(windowMinutes) * 60\n        let currentResetsAt = now.addingTimeInterval(duration / 2)\n        let window = RateWindow(\n            usedPercent: 50,\n            windowMinutes: windowMinutes,\n            resetsAt: currentResetsAt,\n            resetDescription: nil)\n\n        let fiveTrueWeeks = (0..<5).map { index in\n            HistoricalWeekProfile(\n                resetsAt: currentResetsAt.addingTimeInterval(-duration * Double(index + 1)),\n                windowMinutes: windowMinutes,\n                curve: Self.linearCurve(end: 100))\n        }\n        let paceAllTrue = CodexHistoricalPaceEvaluator.evaluate(\n            window: window,\n            now: now,\n            dataset: CodexHistoricalDataset(weeks: fiveTrueWeeks))\n        let probabilityTrue = try #require(paceAllTrue?.runOutProbability)\n        #expect(probabilityTrue > 0)\n        #expect(probabilityTrue < 1)\n\n        let fiveFalseWeeks = (0..<5).map { index in\n            HistoricalWeekProfile(\n                resetsAt: currentResetsAt.addingTimeInterval(-duration * Double(index + 1)),\n                windowMinutes: windowMinutes,\n                curve: Self.linearCurve(end: 80))\n        }\n        let paceAllFalse = CodexHistoricalPaceEvaluator.evaluate(\n            window: window,\n            now: now,\n            dataset: CodexHistoricalDataset(weeks: fiveFalseWeeks))\n        let probabilityFalse = try #require(paceAllFalse?.runOutProbability)\n        #expect(probabilityFalse > 0)\n        #expect(probabilityFalse < 1)\n\n        let fourWeeks = Array(fiveTrueWeeks.prefix(4))\n        let paceFourWeeks = CodexHistoricalPaceEvaluator.evaluate(\n            window: window,\n            now: now,\n            dataset: CodexHistoricalDataset(weeks: fourWeeks))\n        #expect(paceFourWeeks?.runOutProbability == nil)\n    }\n\n    @Test\n    func `evaluator never returns negative eta with outlier week`() {\n        let now = Date(timeIntervalSince1970: 0)\n        let windowMinutes = 10080\n        let duration = TimeInterval(windowMinutes) * 60\n        let currentResetsAt = now.addingTimeInterval(duration / 2)\n        let window = RateWindow(\n            usedPercent: 50,\n            windowMinutes: windowMinutes,\n            resetsAt: currentResetsAt,\n            resetDescription: nil)\n\n        var weeks = (0..<4).map { index in\n            HistoricalWeekProfile(\n                resetsAt: currentResetsAt.addingTimeInterval(-duration * Double(index + 1)),\n                windowMinutes: windowMinutes,\n                curve: Self.linearCurve(end: 100))\n        }\n        weeks.append(HistoricalWeekProfile(\n            resetsAt: currentResetsAt.addingTimeInterval(-duration * 5),\n            windowMinutes: windowMinutes,\n            curve: Self.outlierCurve()))\n\n        let pace = CodexHistoricalPaceEvaluator.evaluate(\n            window: window,\n            now: now,\n            dataset: CodexHistoricalDataset(weeks: weeks))\n\n        #expect(pace != nil)\n        #expect((pace?.etaSeconds ?? 0) >= 0)\n    }\n\n    @Test\n    func `history store backfills from usage breakdown when history is empty`() async {\n        let fileURL = Self.makeTempURL()\n        let store = HistoricalUsageHistoryStore(fileURL: fileURL)\n        let now = Date(timeIntervalSince1970: 1_770_000_000)\n        let windowMinutes = 10080\n        let resetsAt = now.addingTimeInterval(2 * 24 * 60 * 60)\n        let referenceWindow = RateWindow(\n            usedPercent: 50,\n            windowMinutes: windowMinutes,\n            resetsAt: resetsAt,\n            resetDescription: nil)\n\n        let breakdown = Self.syntheticBreakdown(endingAt: now, days: 35, dailyCredits: 10)\n        let dataset = await store.backfillCodexWeeklyFromUsageBreakdown(\n            breakdown,\n            referenceWindow: referenceWindow,\n            now: now,\n            accountKey: nil)\n\n        #expect((dataset?.weeks.count ?? 0) >= 3)\n    }\n\n    @Test\n    func `history store backfill is idempotent for existing weeks`() async {\n        let fileURL = Self.makeTempURL()\n        let store = HistoricalUsageHistoryStore(fileURL: fileURL)\n        let now = Date(timeIntervalSince1970: 1_770_000_000)\n        let windowMinutes = 10080\n        let resetsAt = now.addingTimeInterval(2 * 24 * 60 * 60)\n        let referenceWindow = RateWindow(\n            usedPercent: 50,\n            windowMinutes: windowMinutes,\n            resetsAt: resetsAt,\n            resetDescription: nil)\n\n        let breakdown = Self.syntheticBreakdown(endingAt: now, days: 35, dailyCredits: 10)\n        let first = await store.backfillCodexWeeklyFromUsageBreakdown(\n            breakdown,\n            referenceWindow: referenceWindow,\n            now: now,\n            accountKey: nil)\n        let recordsAfterFirst = (try? Self.readHistoricalRecords(from: fileURL)) ?? []\n        let second = await store.backfillCodexWeeklyFromUsageBreakdown(\n            breakdown,\n            referenceWindow: referenceWindow,\n            now: now,\n            accountKey: nil)\n        let recordsAfterSecond = (try? Self.readHistoricalRecords(from: fileURL)) ?? []\n\n        #expect((first?.weeks.count ?? 0) >= 3)\n        #expect(first?.weeks.count == second?.weeks.count)\n        #expect(recordsAfterSecond.count == recordsAfterFirst.count)\n        #expect(Self.recordDedupKeyCount(recordsAfterSecond) == recordsAfterSecond.count)\n        #expect(Self.datasetCurveSignature(first) == Self.datasetCurveSignature(second))\n    }\n\n    @Test\n    func `history store backfill fills incomplete existing week`() async {\n        let fileURL = Self.makeTempURL()\n        let store = HistoricalUsageHistoryStore(fileURL: fileURL)\n        let now = Date(timeIntervalSince1970: 1_770_000_000)\n        let windowMinutes = 10080\n        let duration = TimeInterval(windowMinutes) * 60\n        let resetsAt = now.addingTimeInterval(2 * 24 * 60 * 60)\n        let referenceWindow = RateWindow(\n            usedPercent: 50,\n            windowMinutes: windowMinutes,\n            resetsAt: resetsAt,\n            resetDescription: nil)\n\n        let incompleteReset = resetsAt.addingTimeInterval(-2 * duration)\n        let incompleteStart = incompleteReset.addingTimeInterval(-duration)\n        _ = await store.recordCodexWeekly(\n            window: RateWindow(\n                usedPercent: 42,\n                windowMinutes: windowMinutes,\n                resetsAt: incompleteReset,\n                resetDescription: nil),\n            sampledAt: incompleteStart.addingTimeInterval(duration * 0.5),\n            accountKey: nil)\n\n        let breakdown = Self.syntheticBreakdown(endingAt: now, days: 29, dailyCredits: 10)\n        let dataset = await store.backfillCodexWeeklyFromUsageBreakdown(\n            breakdown,\n            referenceWindow: referenceWindow,\n            now: now,\n            accountKey: nil)\n\n        #expect((dataset?.weeks.count ?? 0) >= 3)\n    }\n\n    @Test\n    func `history store should accept does not cross window minutes regimes`() async throws {\n        let fileURL = Self.makeTempURL()\n        let store = HistoricalUsageHistoryStore(fileURL: fileURL)\n        let sampledAt = Date(timeIntervalSince1970: 1_770_000_000)\n        let resetsAt = sampledAt.addingTimeInterval(3 * 24 * 60 * 60)\n\n        _ = await store.recordCodexWeekly(\n            window: RateWindow(\n                usedPercent: 20,\n                windowMinutes: 10080,\n                resetsAt: resetsAt,\n                resetDescription: nil),\n            sampledAt: sampledAt,\n            accountKey: nil)\n        _ = await store.recordCodexWeekly(\n            window: RateWindow(\n                usedPercent: 20,\n                windowMinutes: 20160,\n                resetsAt: resetsAt,\n                resetDescription: nil),\n            sampledAt: sampledAt.addingTimeInterval(60),\n            accountKey: nil)\n\n        let data = try Data(contentsOf: fileURL)\n        let text = String(data: data, encoding: .utf8) ?? \"\"\n        let lines = text\n            .split(separator: \"\\n\", omittingEmptySubsequences: true)\n            .map(String.init)\n        #expect(lines.count == 2)\n    }\n\n    @Test\n    func `weeks with reset jitter are grouped together`() async {\n        let fileURL = Self.makeTempURL()\n        let store = HistoricalUsageHistoryStore(fileURL: fileURL)\n        let windowMinutes = 10080\n        let duration = TimeInterval(windowMinutes) * 60\n        let canonicalReset = Date(timeIntervalSince1970: 1_770_000_000)\n        let windowStart = canonicalReset.addingTimeInterval(-duration)\n\n        let samples: [(u: Double, used: Double)] = [\n            (0.02, 2),\n            (0.10, 10),\n            (0.30, 30),\n            (0.50, 50),\n            (0.80, 80),\n            (0.98, 95),\n        ]\n        for (index, sample) in samples.enumerated() {\n            let jitteredReset = canonicalReset.addingTimeInterval(index.isMultiple(of: 2) ? -20 : 20)\n            _ = await store.recordCodexWeekly(\n                window: RateWindow(\n                    usedPercent: sample.used,\n                    windowMinutes: windowMinutes,\n                    resetsAt: jitteredReset,\n                    resetDescription: nil),\n                sampledAt: windowStart.addingTimeInterval(sample.u * duration),\n                accountKey: nil)\n        }\n\n        let dataset = await store.loadCodexDataset(accountKey: nil)\n        #expect(dataset?.weeks.count == 1)\n    }\n\n    @Test\n    func `backfill matches incomplete week when reset jitter exists`() async throws {\n        let fileURL = Self.makeTempURL()\n        let store = HistoricalUsageHistoryStore(fileURL: fileURL)\n        let now = Date(timeIntervalSince1970: 1_770_000_000)\n        let windowMinutes = 10080\n        let duration = TimeInterval(windowMinutes) * 60\n        let currentReset = now.addingTimeInterval(2 * 24 * 60 * 60)\n        let targetReset = currentReset.addingTimeInterval(-2 * duration)\n        let targetStart = targetReset.addingTimeInterval(-duration)\n\n        _ = await store.recordCodexWeekly(\n            window: RateWindow(\n                usedPercent: 35,\n                windowMinutes: windowMinutes,\n                resetsAt: targetReset.addingTimeInterval(30),\n                resetDescription: nil),\n            sampledAt: targetStart.addingTimeInterval(duration * 0.5),\n            accountKey: nil)\n\n        let datasetBefore = await store.loadCodexDataset(accountKey: nil)\n        #expect(datasetBefore == nil)\n\n        let referenceWindow = RateWindow(\n            usedPercent: 50,\n            windowMinutes: windowMinutes,\n            resetsAt: currentReset,\n            resetDescription: nil)\n        let breakdown = Self.syntheticBreakdown(endingAt: now, days: 29, dailyCredits: 10)\n        let datasetAfter = await store.backfillCodexWeeklyFromUsageBreakdown(\n            breakdown,\n            referenceWindow: referenceWindow,\n            now: now,\n            accountKey: nil)\n        #expect((datasetAfter?.weeks.count ?? 0) >= 3)\n\n        let normalizedTargetReset = Self.normalizeReset(targetReset)\n        let records = try Self.readHistoricalRecords(from: fileURL)\n        let matching = records.filter { $0.accountKey == nil && $0.resetsAt == normalizedTargetReset }\n        #expect(matching.count > 1)\n    }\n\n    @Test\n    func `backfill does not stop at three weeks when more backfillable weeks exist`() async throws {\n        let fileURL = Self.makeTempURL()\n        let store = HistoricalUsageHistoryStore(fileURL: fileURL)\n        let now = Date(timeIntervalSince1970: 1_770_000_000)\n        let windowMinutes = 10080\n        let duration = TimeInterval(windowMinutes) * 60\n        let currentReset = now.addingTimeInterval(2 * 24 * 60 * 60)\n\n        // Seed 3 complete weeks.\n        for weekOffset in 1...3 {\n            let reset = currentReset.addingTimeInterval(-duration * Double(weekOffset))\n            let start = reset.addingTimeInterval(-duration)\n            let samples: [(u: Double, used: Double)] = [\n                (0.02, 2), (0.10, 10), (0.30, 30), (0.50, 50), (0.80, 80), (0.98, 95),\n            ]\n            for sample in samples {\n                _ = await store.recordCodexWeekly(\n                    window: RateWindow(\n                        usedPercent: sample.used,\n                        windowMinutes: windowMinutes,\n                        resetsAt: reset,\n                        resetDescription: nil),\n                    sampledAt: start.addingTimeInterval(sample.u * duration),\n                    accountKey: nil)\n            }\n        }\n\n        let pre = try #require(await store.loadCodexDataset(accountKey: nil))\n        #expect(pre.weeks.count == 3)\n\n        let referenceWindow = RateWindow(\n            usedPercent: 50,\n            windowMinutes: windowMinutes,\n            resetsAt: currentReset,\n            resetDescription: nil)\n        let breakdown = Self.syntheticBreakdown(endingAt: now, days: 56, dailyCredits: 10)\n        let post = await store.backfillCodexWeeklyFromUsageBreakdown(\n            breakdown,\n            referenceWindow: referenceWindow,\n            now: now,\n            accountKey: nil)\n\n        #expect((post?.weeks.count ?? 0) > 3)\n    }\n\n    @Test\n    func `load dataset uses only current account key`() async throws {\n        let fileURL = Self.makeTempURL()\n        let store = HistoricalUsageHistoryStore(fileURL: fileURL)\n        let windowMinutes = 10080\n        let duration = TimeInterval(windowMinutes) * 60\n        let reset = Date(timeIntervalSince1970: 1_770_000_000)\n        let start = reset.addingTimeInterval(-duration)\n        let samples: [Double] = [0.02, 0.10, 0.30, 0.50, 0.80, 0.98]\n\n        for u in samples {\n            _ = await store.recordCodexWeekly(\n                window: RateWindow(\n                    usedPercent: u * 100,\n                    windowMinutes: windowMinutes,\n                    resetsAt: reset,\n                    resetDescription: nil),\n                sampledAt: start.addingTimeInterval(u * duration),\n                accountKey: \"acct-a\")\n        }\n        for u in samples {\n            _ = await store.recordCodexWeekly(\n                window: RateWindow(\n                    usedPercent: min(100, (u * 100) + 10),\n                    windowMinutes: windowMinutes,\n                    resetsAt: reset,\n                    resetDescription: nil),\n                sampledAt: start.addingTimeInterval(u * duration),\n                accountKey: \"acct-b\")\n        }\n\n        let aDataset = try #require(await store.loadCodexDataset(accountKey: \"acct-a\"))\n        let bDataset = try #require(await store.loadCodexDataset(accountKey: \"acct-b\"))\n        let nilDataset = await store.loadCodexDataset(accountKey: nil)\n        #expect(aDataset.weeks.count == 1)\n        #expect(bDataset.weeks.count == 1)\n        #expect(nilDataset == nil)\n        #expect(abs(aDataset.weeks[0].curve[84] - bDataset.weeks[0].curve[84]) > 0.1)\n    }\n\n    @Test\n    func `backfill filters by account key`() async throws {\n        let fileURL = Self.makeTempURL()\n        let store = HistoricalUsageHistoryStore(fileURL: fileURL)\n        let now = Date(timeIntervalSince1970: 1_770_000_000)\n        let resetsAt = now.addingTimeInterval(2 * 24 * 60 * 60)\n        let referenceWindow = RateWindow(\n            usedPercent: 50,\n            windowMinutes: 10080,\n            resetsAt: resetsAt,\n            resetDescription: nil)\n        let breakdown = Self.syntheticBreakdown(endingAt: now, days: 35, dailyCredits: 10)\n\n        _ = await store.backfillCodexWeeklyFromUsageBreakdown(\n            breakdown,\n            referenceWindow: referenceWindow,\n            now: now,\n            accountKey: \"acct-a\")\n        _ = await store.backfillCodexWeeklyFromUsageBreakdown(\n            breakdown,\n            referenceWindow: referenceWindow,\n            now: now,\n            accountKey: \"acct-b\")\n\n        let records = try Self.readHistoricalRecords(from: fileURL)\n        #expect(records.contains { $0.accountKey == \"acct-a\" })\n        #expect(records.contains { $0.accountKey == \"acct-b\" })\n\n        let aDataset = await store.loadCodexDataset(accountKey: \"acct-a\")\n        let bDataset = await store.loadCodexDataset(accountKey: \"acct-b\")\n        #expect((aDataset?.weeks.count ?? 0) >= 3)\n        #expect((bDataset?.weeks.count ?? 0) >= 3)\n    }\n\n    @Test\n    func `coverage tolerance allows day boundary shift without no op`() async {\n        let fileURL = Self.makeTempURL()\n        let store = HistoricalUsageHistoryStore(fileURL: fileURL)\n        let now = Date(timeIntervalSince1970: 1_770_000_000)\n        let breakdown = Self.syntheticBreakdown(endingAt: now, days: 35, dailyCredits: 10)\n\n        let coverageStart = Self.dayStart(for: breakdown.last?.day ?? \"\")!\n        let priorBackfillStart = coverageStart.addingTimeInterval(-10 * 60 * 60)\n        let priorBackfillReset = priorBackfillStart.addingTimeInterval(7 * 24 * 60 * 60)\n        let resetsAt = priorBackfillReset.addingTimeInterval(7 * 24 * 60 * 60)\n        let referenceWindow = RateWindow(\n            usedPercent: 50,\n            windowMinutes: 10080,\n            resetsAt: resetsAt,\n            resetDescription: nil)\n\n        let dataset = await store.backfillCodexWeeklyFromUsageBreakdown(\n            breakdown,\n            referenceWindow: referenceWindow,\n            now: now,\n            accountKey: nil)\n        #expect((dataset?.weeks.count ?? 0) >= 1)\n    }\n\n    @Test\n    func `backfill treats omitted recent zero usage days as coverage`() async {\n        let fileURL = Self.makeTempURL()\n        let store = HistoricalUsageHistoryStore(fileURL: fileURL)\n        let now = Self.gregorianDate(year: 2026, month: 2, day: 26, hour: 20)\n        let resetsAt = now.addingTimeInterval(2 * 24 * 60 * 60)\n        let referenceWindow = RateWindow(\n            usedPercent: 50,\n            windowMinutes: 10080,\n            resetsAt: resetsAt,\n            resetDescription: nil)\n\n        let breakdown = Self.syntheticBreakdown(\n            endingAt: now,\n            days: 35,\n            dailyCredits: 10,\n            overridesByDayOffset: [\n                0: 0,\n                1: 0,\n            ])\n            .filter { $0.totalCreditsUsed > 0 }\n\n        let dataset = await store.backfillCodexWeeklyFromUsageBreakdown(\n            breakdown,\n            referenceWindow: referenceWindow,\n            now: now,\n            accountKey: nil)\n\n        #expect((dataset?.weeks.count ?? 0) >= 3)\n    }\n\n    @Test\n    func `backfill treats omitted leading zero usage days as coverage`() async {\n        let fileURL = Self.makeTempURL()\n        let store = HistoricalUsageHistoryStore(fileURL: fileURL)\n        let now = Self.gregorianDate(year: 2026, month: 2, day: 26, hour: 20)\n        let resetsAt = now.addingTimeInterval(2 * 24 * 60 * 60)\n        let referenceWindow = RateWindow(\n            usedPercent: 50,\n            windowMinutes: 10080,\n            resetsAt: resetsAt,\n            resetDescription: nil)\n\n        let breakdown = Self.syntheticBreakdown(\n            endingAt: now,\n            days: 35,\n            dailyCredits: 10,\n            overridesByDayOffset: [\n                3: 0,\n                4: 0,\n                5: 0,\n            ])\n            .filter { $0.totalCreditsUsed > 0 }\n\n        let dataset = await store.backfillCodexWeeklyFromUsageBreakdown(\n            breakdown,\n            referenceWindow: referenceWindow,\n            now: now,\n            accountKey: nil)\n\n        #expect((dataset?.weeks.count ?? 0) >= 3)\n    }\n\n    @Test\n    func `partial day credits are not undercounted at as of time`() {\n        let asOf = Self.gregorianDate(year: 2026, month: 2, day: 26, hour: 12)\n        let start = Self.gregorianDate(year: 2026, month: 2, day: 20, hour: 0)\n        let breakdown = Self.syntheticBreakdown(\n            endingAt: asOf,\n            days: 35,\n            dailyCredits: 1,\n            overridesByDayOffset: [\n                0: 20,\n                7: 20,\n            ])\n\n        let credits = HistoricalUsageHistoryStore._creditsUsedForTesting(\n            breakdown: breakdown,\n            asOf: asOf,\n            start: start,\n            end: asOf)\n\n        #expect(abs(credits - 26) < 0.001)\n    }\n\n    @Test\n    func `gregorian day parsing is stable for YYYYMMDD`() {\n        let parsed = HistoricalUsageHistoryStore._dayStartForTesting(\"2026-02-26\")\n        let expected = Self.gregorianDate(year: 2026, month: 2, day: 26, hour: 0)\n        #expect(parsed == expected)\n    }\n\n    @MainActor\n    @Test\n    func `backfill skips when timestamp mismatch exceeds5 minutes`() async throws {\n        let store = try Self.makeUsageStoreForBackfillTests(\n            suite: \"HistoricalUsagePaceTests-backfill-mismatch\",\n            historyFileURL: Self.makeTempURL())\n        store._setCodexHistoricalDatasetForTesting(nil)\n\n        let snapshotNow = Date(timeIntervalSince1970: 1_770_000_000)\n        let weekly = RateWindow(\n            usedPercent: 55,\n            windowMinutes: 10080,\n            resetsAt: snapshotNow.addingTimeInterval(2 * 24 * 60 * 60),\n            resetDescription: nil)\n        let snapshot = UsageSnapshot(\n            primary: nil,\n            secondary: weekly,\n            tertiary: nil,\n            providerCost: nil,\n            updatedAt: snapshotNow,\n            identity: nil)\n        store._setSnapshotForTesting(snapshot, provider: .codex)\n\n        let dashboard = OpenAIDashboardSnapshot(\n            signedInEmail: nil,\n            codeReviewRemainingPercent: nil,\n            creditEvents: [],\n            dailyBreakdown: [],\n            usageBreakdown: Self.syntheticBreakdown(endingAt: snapshotNow, days: 35, dailyCredits: 10),\n            creditsPurchaseURL: nil,\n            primaryLimit: nil,\n            secondaryLimit: nil,\n            creditsRemaining: nil,\n            accountPlan: nil,\n            updatedAt: snapshotNow.addingTimeInterval(-10 * 60))\n        store.backfillCodexHistoricalFromDashboardIfNeeded(dashboard)\n\n        try await Task.sleep(for: .milliseconds(250))\n        #expect(store.codexHistoricalDataset == nil)\n    }\n\n    @MainActor\n    @Test\n    func `backfill uses dashboard secondary when available`() async throws {\n        let store = try Self.makeUsageStoreForBackfillTests(\n            suite: \"HistoricalUsagePaceTests-backfill-dashboard-secondary\",\n            historyFileURL: Self.makeTempURL())\n        store._setCodexHistoricalDatasetForTesting(nil)\n\n        let snapshotNow = Date(timeIntervalSince1970: 1_770_000_000)\n        let staleSnapshot = UsageSnapshot(\n            primary: nil,\n            secondary: RateWindow(\n                usedPercent: 5,\n                windowMinutes: 10080,\n                resetsAt: snapshotNow.addingTimeInterval(2 * 24 * 60 * 60),\n                resetDescription: nil),\n            tertiary: nil,\n            providerCost: nil,\n            updatedAt: snapshotNow.addingTimeInterval(-30 * 60),\n            identity: nil)\n        store._setSnapshotForTesting(staleSnapshot, provider: .codex)\n\n        let dashboard = OpenAIDashboardSnapshot(\n            signedInEmail: nil,\n            codeReviewRemainingPercent: nil,\n            creditEvents: [],\n            dailyBreakdown: [],\n            usageBreakdown: Self.syntheticBreakdown(endingAt: snapshotNow, days: 35, dailyCredits: 10),\n            creditsPurchaseURL: nil,\n            primaryLimit: nil,\n            secondaryLimit: RateWindow(\n                usedPercent: 50,\n                windowMinutes: 10080,\n                resetsAt: snapshotNow.addingTimeInterval(2 * 24 * 60 * 60),\n                resetDescription: nil),\n            creditsRemaining: nil,\n            accountPlan: nil,\n            updatedAt: snapshotNow)\n        store.backfillCodexHistoricalFromDashboardIfNeeded(dashboard)\n\n        for _ in 0..<40 {\n            if (store.codexHistoricalDataset?.weeks.count ?? 0) >= 3 {\n                break\n            }\n            try await Task.sleep(for: .milliseconds(50))\n        }\n        #expect((store.codexHistoricalDataset?.weeks.count ?? 0) >= 3)\n    }\n\n    @Test\n    func `will last decision uses smoothed probability when risk hidden`() throws {\n        let now = Date(timeIntervalSince1970: 0)\n        let windowMinutes = 10080\n        let duration = TimeInterval(windowMinutes) * 60\n        let currentResetsAt = now.addingTimeInterval(duration / 2)\n        let window = RateWindow(\n            usedPercent: 50,\n            windowMinutes: windowMinutes,\n            resetsAt: currentResetsAt,\n            resetDescription: nil)\n\n        let weeks = (0..<4).map { index in\n            HistoricalWeekProfile(\n                resetsAt: currentResetsAt.addingTimeInterval(-duration * Double(index + 1)),\n                windowMinutes: windowMinutes,\n                curve: Self.linearCurve(end: 100))\n        }\n        let pace = try #require(CodexHistoricalPaceEvaluator.evaluate(\n            window: window,\n            now: now,\n            dataset: CodexHistoricalDataset(weeks: weeks)))\n        #expect(pace.runOutProbability == nil)\n\n        let totalWeight = weeks.enumerated().reduce(0.0) { partial, element in\n            let ageWeeks = currentResetsAt.timeIntervalSince(element.element.resetsAt) / duration\n            return partial + exp(-ageWeeks / 3.0)\n        }\n        let smoothedProbability = (totalWeight + 0.5) / (totalWeight + 1.0)\n        #expect(pace.willLastToReset == (smoothedProbability < 0.5))\n    }\n\n    @MainActor\n    @Test\n    func `usage store falls back to linear when history disabled or insufficient`() throws {\n        let suite = \"HistoricalUsagePaceTests-usage-store\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let settings = SettingsStore(\n            userDefaults: defaults,\n            configStore: testConfigStore(suiteName: suite),\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore(),\n            codexCookieStore: InMemoryCookieHeaderStore(),\n            claudeCookieStore: InMemoryCookieHeaderStore(),\n            cursorCookieStore: InMemoryCookieHeaderStore(),\n            opencodeCookieStore: InMemoryCookieHeaderStore(),\n            factoryCookieStore: InMemoryCookieHeaderStore(),\n            minimaxCookieStore: InMemoryMiniMaxCookieStore(),\n            minimaxAPITokenStore: InMemoryMiniMaxAPITokenStore(),\n            kimiTokenStore: InMemoryKimiTokenStore(),\n            kimiK2TokenStore: InMemoryKimiK2TokenStore(),\n            augmentCookieStore: InMemoryCookieHeaderStore(),\n            ampCookieStore: InMemoryCookieHeaderStore(),\n            copilotTokenStore: InMemoryCopilotTokenStore(),\n            tokenAccountStore: InMemoryTokenAccountStore())\n        settings.historicalTrackingEnabled = true\n\n        let store = UsageStore(\n            fetcher: UsageFetcher(environment: [:]),\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            settings: settings,\n            historicalUsageHistoryStore: HistoricalUsageHistoryStore(fileURL: Self.makeTempURL()))\n\n        let now = Date(timeIntervalSince1970: 0)\n        let window = RateWindow(\n            usedPercent: 50,\n            windowMinutes: 10080,\n            resetsAt: now.addingTimeInterval(4 * 24 * 60 * 60),\n            resetDescription: nil)\n\n        let twoWeeksDataset = CodexHistoricalDataset(weeks: [\n            HistoricalWeekProfile(\n                resetsAt: now.addingTimeInterval(-7 * 24 * 60 * 60),\n                windowMinutes: 10080,\n                curve: Self.linearCurve(end: 100)),\n            HistoricalWeekProfile(\n                resetsAt: now.addingTimeInterval(-14 * 24 * 60 * 60),\n                windowMinutes: 10080,\n                curve: Self.linearCurve(end: 100)),\n        ])\n        store._setCodexHistoricalDatasetForTesting(twoWeeksDataset)\n\n        let computed = store.weeklyPace(provider: .codex, window: window, now: now)\n        let linear = UsagePace.weekly(window: window, now: now, defaultWindowMinutes: 10080)\n        #expect(computed != nil)\n        #expect(abs((computed?.deltaPercent ?? 0) - (linear?.deltaPercent ?? 0)) < 0.001)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/InstallOriginTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBar\n\nstruct InstallOriginTests {\n    @Test\n    func `detects homebrew caskroom`() {\n        #expect(\n            InstallOrigin\n                .isHomebrewCask(\n                    appBundleURL: URL(fileURLWithPath: \"/opt/homebrew/Caskroom/codexbar/1.0.0/CodexBar.app\")))\n        #expect(\n            InstallOrigin\n                .isHomebrewCask(appBundleURL: URL(fileURLWithPath: \"/usr/local/Caskroom/codexbar/1.0.0/CodexBar.app\")))\n        #expect(!InstallOrigin.isHomebrewCask(appBundleURL: URL(fileURLWithPath: \"/Applications/CodexBar.app\")))\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/JetBrainsIDEDetectorTests.swift",
    "content": "import CodexBarCore\nimport Testing\n\nstruct JetBrainsIDEDetectorTests {\n    @Test\n    func `parses IDE directory case insensitive`() {\n        let info = JetBrainsIDEDetector._parseIDEDirectoryForTesting(\n            dirname: \"Webstorm2024.1\",\n            basePath: \"/test\")\n\n        #expect(info?.name == \"WebStorm\")\n        #expect(info?.version == \"2024.1\")\n        #expect(info?.basePath == \"/test/Webstorm2024.1\")\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/JetBrainsStatusProbeTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n\nstruct JetBrainsStatusProbeTests {\n    @Test\n    func `parses quota XML with tariff quota`() throws {\n        // Real-world format with tariffQuota containing available credits\n        let quotaInfo = [\n            \"{&#10;  &quot;type&quot;: &quot;Available&quot;,\",\n            \"&#10;  &quot;current&quot;: &quot;7478.3&quot;,\",\n            \"&#10;  &quot;maximum&quot;: &quot;1000000&quot;,\",\n            \"&#10;  &quot;until&quot;: &quot;2026-11-09T21:00:00Z&quot;,\",\n            \"&#10;  &quot;tariffQuota&quot;: {\",\n            \"&#10;    &quot;current&quot;: &quot;7478.3&quot;,\",\n            \"&#10;    &quot;maximum&quot;: &quot;1000000&quot;,\",\n            \"&#10;    &quot;available&quot;: &quot;992521.7&quot;\",\n            \"&#10;  }&#10;}\",\n        ].joined()\n        let nextRefill = [\n            \"{&#10;  &quot;type&quot;: &quot;Known&quot;,\",\n            \"&#10;  &quot;next&quot;: &quot;2026-01-16T14:00:54.939Z&quot;,\",\n            \"&#10;  &quot;tariff&quot;: {\",\n            \"&#10;    &quot;amount&quot;: &quot;1000000&quot;,\",\n            \"&#10;    &quot;duration&quot;: &quot;PT720H&quot;\",\n            \"&#10;  }&#10;}\",\n        ].joined()\n\n        let xml = \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <application>\n          <component name=\"AIAssistantQuotaManager2\">\n            <option\n              name=\"quotaInfo\"\n              value=\"\\(quotaInfo)\" />\n            <option\n              name=\"nextRefill\"\n              value=\"\\(nextRefill)\" />\n          </component>\n        </application>\n        \"\"\"\n\n        let data = Data(xml.utf8)\n        let snapshot = try JetBrainsStatusProbe.parseXMLData(data, detectedIDE: nil)\n\n        #expect(snapshot.quotaInfo.type == \"Available\")\n        #expect(snapshot.quotaInfo.used == 7478.3)\n        #expect(snapshot.quotaInfo.maximum == 1_000_000)\n        #expect(snapshot.quotaInfo.available == 992_521.7)\n        #expect(snapshot.quotaInfo.until != nil)\n\n        #expect(snapshot.refillInfo?.type == \"Known\")\n        #expect(snapshot.refillInfo?.amount == 1_000_000)\n        #expect(snapshot.refillInfo?.duration == \"PT720H\")\n        #expect(snapshot.refillInfo?.next != nil)\n    }\n\n    @Test\n    func `parses quota XML without tariff quota`() throws {\n        // Fallback format without tariffQuota\n        let quotaInfo = [\n            \"{&#10;  &quot;type&quot;: &quot;paid&quot;,\",\n            \"&#10;  &quot;current&quot;: &quot;50000&quot;,\",\n            \"&#10;  &quot;maximum&quot;: &quot;100000&quot;,\",\n            \"&#10;  &quot;until&quot;: &quot;2025-12-31T23:59:59Z&quot;&#10;}\",\n        ].joined()\n        let nextRefill = [\n            \"{&#10;  &quot;type&quot;: &quot;monthly&quot;,\",\n            \"&#10;  &quot;next&quot;: &quot;2025-01-01T00:00:00Z&quot;,\",\n            \"&#10;  &quot;tariff&quot;: {\",\n            \"&#10;    &quot;amount&quot;: &quot;100000&quot;,\",\n            \"&#10;    &quot;duration&quot;: &quot;monthly&quot;\",\n            \"&#10;  }&#10;}\",\n        ].joined()\n\n        let xml = \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <application>\n          <component name=\"AIAssistantQuotaManager2\">\n            <option\n              name=\"quotaInfo\"\n              value=\"\\(quotaInfo)\" />\n            <option\n              name=\"nextRefill\"\n              value=\"\\(nextRefill)\" />\n          </component>\n        </application>\n        \"\"\"\n\n        let data = Data(xml.utf8)\n        let snapshot = try JetBrainsStatusProbe.parseXMLData(data, detectedIDE: nil)\n\n        #expect(snapshot.quotaInfo.type == \"paid\")\n        #expect(snapshot.quotaInfo.used == 50000)\n        #expect(snapshot.quotaInfo.maximum == 100_000)\n        // Without tariffQuota, available is calculated as maximum - used\n        #expect(snapshot.quotaInfo.available == 50000)\n        #expect(snapshot.quotaInfo.until != nil)\n\n        #expect(snapshot.refillInfo?.type == \"monthly\")\n        #expect(snapshot.refillInfo?.amount == 100_000)\n        #expect(snapshot.refillInfo?.duration == \"monthly\")\n    }\n\n    @Test\n    func `calculates usage percentage from available`() {\n        // available = 75_000, maximum = 100_000 -> 75% remaining, 25% used\n        let quotaInfo = JetBrainsQuotaInfo(\n            type: \"paid\",\n            used: 25000,\n            maximum: 100_000,\n            available: 75000,\n            until: nil)\n\n        #expect(quotaInfo.usedPercent == 25.0)\n        #expect(quotaInfo.remainingPercent == 75.0)\n    }\n\n    @Test\n    func `calculates usage percentage at zero`() {\n        let quotaInfo = JetBrainsQuotaInfo(\n            type: \"paid\",\n            used: 0,\n            maximum: 100_000,\n            available: 100_000,\n            until: nil)\n\n        #expect(quotaInfo.usedPercent == 0.0)\n        #expect(quotaInfo.remainingPercent == 100.0)\n    }\n\n    @Test\n    func `calculates usage percentage at max`() {\n        let quotaInfo = JetBrainsQuotaInfo(\n            type: \"paid\",\n            used: 100_000,\n            maximum: 100_000,\n            available: 0,\n            until: nil)\n\n        #expect(quotaInfo.usedPercent == 100.0)\n        #expect(quotaInfo.remainingPercent == 0.0)\n    }\n\n    @Test\n    func `handles zero maximum`() {\n        let quotaInfo = JetBrainsQuotaInfo(\n            type: \"free\",\n            used: 1000,\n            maximum: 0,\n            available: nil,\n            until: nil)\n\n        #expect(quotaInfo.usedPercent == 0.0)\n        #expect(quotaInfo.remainingPercent == 100.0)\n    }\n\n    @Test\n    func `converts to usage snapshot`() throws {\n        let quotaInfo = JetBrainsQuotaInfo(\n            type: \"Available\",\n            used: 7478.3,\n            maximum: 1_000_000,\n            available: 992_521.7,\n            until: Date().addingTimeInterval(3600))\n\n        let refillInfo = JetBrainsRefillInfo(\n            type: \"Known\",\n            next: Date().addingTimeInterval(86400),\n            amount: 1_000_000,\n            duration: \"PT720H\")\n\n        let ideInfo = JetBrainsIDEInfo(\n            name: \"IntelliJ IDEA\",\n            version: \"2025.3\",\n            basePath: \"/test/path\",\n            quotaFilePath: \"/test/path/options/AIAssistantQuotaManager2.xml\")\n\n        let snapshot = JetBrainsStatusSnapshot(\n            quotaInfo: quotaInfo,\n            refillInfo: refillInfo,\n            detectedIDE: ideInfo)\n\n        let usage = try snapshot.toUsageSnapshot()\n\n        #expect(usage.primary != nil)\n        // usedPercent should be approximately 0.75% (7_478.3 / 1_000_000 * 100)\n        #expect(try #require(usage.primary?.usedPercent) < 1.0)\n        // Reset date should come from refillInfo.next, not quotaInfo.until\n        #expect(usage.primary?.resetsAt != nil)\n        #expect(usage.secondary == nil)\n        #expect(usage.identity?.providerID == .jetbrains)\n        #expect(usage.identity?.accountOrganization == \"IntelliJ IDEA 2025.3\")\n        #expect(usage.identity?.loginMethod == \"Available\")\n    }\n\n    @Test\n    func `usage snapshot uses refill date for reset`() throws {\n        let refillDate = Date().addingTimeInterval(86400 * 6) // 6 days from now\n        let untilDate = Date().addingTimeInterval(86400 * 300) // 300 days from now\n\n        let quotaInfo = JetBrainsQuotaInfo(\n            type: \"Available\",\n            used: 1000,\n            maximum: 1_000_000,\n            available: 999_000,\n            until: untilDate)\n\n        let refillInfo = JetBrainsRefillInfo(\n            type: \"Known\",\n            next: refillDate,\n            amount: 1_000_000,\n            duration: \"PT720H\")\n\n        let snapshot = JetBrainsStatusSnapshot(\n            quotaInfo: quotaInfo,\n            refillInfo: refillInfo,\n            detectedIDE: nil)\n\n        let usage = try snapshot.toUsageSnapshot()\n\n        // Reset date should be refillDate (6 days), not untilDate (300 days)\n        #expect(usage.primary?.resetsAt == refillDate)\n    }\n\n    @Test\n    func `parses IDE directory`() {\n        let ides = [\n            (\"IntelliJIdea2024.3\", \"IntelliJ IDEA\", \"2024.3\"),\n            (\"PyCharm2024.2\", \"PyCharm\", \"2024.2\"),\n            (\"WebStorm2024.1\", \"WebStorm\", \"2024.1\"),\n            (\"GoLand2024.3\", \"GoLand\", \"2024.3\"),\n            (\"CLion2024.2\", \"CLion\", \"2024.2\"),\n            (\"RustRover2024.3\", \"RustRover\", \"2024.3\"),\n        ]\n\n        for (dirname, expectedName, expectedVersion) in ides {\n            let info = JetBrainsIDEInfo(\n                name: expectedName,\n                version: expectedVersion,\n                basePath: \"/test/\\(dirname)\",\n                quotaFilePath: \"/test/\\(dirname)/options/AIAssistantQuotaManager2.xml\")\n\n            #expect(info.name == expectedName)\n            #expect(info.version == expectedVersion)\n            #expect(info.displayName == \"\\(expectedName) \\(expectedVersion)\")\n        }\n    }\n\n    @Test\n    func `expands tilde in custom path`() async throws {\n        let fileManager = FileManager.default\n        let home = fileManager.homeDirectoryForCurrentUser\n        let testRoot = home\n            .appendingPathComponent(\"Library\")\n            .appendingPathComponent(\"Caches\")\n            .appendingPathComponent(\"CodexBarTests\")\n            .appendingPathComponent(\"JetBrains-\\(UUID().uuidString)\")\n        let optionsDir = testRoot.appendingPathComponent(\"options\")\n        try fileManager.createDirectory(\n            at: optionsDir,\n            withIntermediateDirectories: true,\n            attributes: nil)\n        defer { try? fileManager.removeItem(at: testRoot) }\n\n        let quotaInfo = [\n            \"{&quot;type&quot;:&quot;free&quot;\",\n            \",&quot;current&quot;:&quot;0&quot;\",\n            \",&quot;maximum&quot;:&quot;100000&quot;}\",\n        ].joined()\n        let xml = \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <application>\n          <component name=\"AIAssistantQuotaManager2\">\n            <option\n              name=\"quotaInfo\"\n              value=\"\\(quotaInfo)\" />\n          </component>\n        </application>\n        \"\"\"\n        let quotaFile = optionsDir.appendingPathComponent(\"AIAssistantQuotaManager2.xml\")\n        try xml.write(to: quotaFile, atomically: true, encoding: .utf8)\n\n        let tildePath: String\n        if testRoot.path.hasPrefix(home.path) {\n            let suffix = testRoot.path.dropFirst(home.path.count)\n            tildePath = \"~\\(suffix)\"\n        } else {\n            tildePath = testRoot.path\n        }\n\n        let settings = ProviderSettingsSnapshot.make(\n            jetbrains: ProviderSettingsSnapshot.JetBrainsProviderSettings(ideBasePath: \"  \\(tildePath)  \"))\n\n        let probe = JetBrainsStatusProbe(settings: settings)\n        let snapshot = try await probe.fetch()\n\n        #expect(snapshot.quotaInfo.maximum == 100_000)\n    }\n\n    @Test\n    func `handles HTML entities`() throws {\n        let quotaInfo = [\n            \"{&quot;type&quot;:&quot;free&quot;\",\n            \",&quot;current&quot;:&quot;0&quot;\",\n            \",&quot;maximum&quot;:&quot;50000&quot;}\",\n        ].joined()\n        let xml = \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <application>\n          <component name=\"AIAssistantQuotaManager2\">\n            <option\n              name=\"quotaInfo\"\n              value=\"\\(quotaInfo)\" />\n          </component>\n        </application>\n        \"\"\"\n\n        let data = Data(xml.utf8)\n        let snapshot = try JetBrainsStatusProbe.parseXMLData(data, detectedIDE: nil)\n\n        #expect(snapshot.quotaInfo.type == \"free\")\n        #expect(snapshot.quotaInfo.used == 0)\n        #expect(snapshot.quotaInfo.maximum == 50000)\n    }\n\n    @Test\n    func `throws on missing quota info`() throws {\n        let xml = \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <application>\n          <component name=\"AIAssistantQuotaManager2\">\n          </component>\n        </application>\n        \"\"\"\n\n        let data = Data(xml.utf8)\n        #expect(throws: JetBrainsStatusProbeError.noQuotaInfo) {\n            _ = try JetBrainsStatusProbe.parseXMLData(data, detectedIDE: nil)\n        }\n    }\n\n    @Test\n    func `throws on empty quota info`() throws {\n        let xml = \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <application>\n          <component name=\"AIAssistantQuotaManager2\">\n            <option name=\"quotaInfo\" value=\"\" />\n          </component>\n        </application>\n        \"\"\"\n\n        let data = Data(xml.utf8)\n        #expect(throws: JetBrainsStatusProbeError.noQuotaInfo) {\n            _ = try JetBrainsStatusProbe.parseXMLData(data, detectedIDE: nil)\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/KeyboardShortcutsBundleTests.swift",
    "content": "import KeyboardShortcuts\nimport Testing\n\n@MainActor\nstruct KeyboardShortcutsBundleTests {\n    @Test func `recorder initializes without crashing`() {\n        _ = KeyboardShortcuts.RecorderCocoa(for: .init(\"test.keyboardshortcuts.bundle\"))\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/KeychainCacheStoreTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\n@Suite(.serialized)\nstruct KeychainCacheStoreTests {\n    struct TestEntry: Codable, Equatable {\n        let value: String\n        let storedAt: Date\n    }\n\n    @Test\n    func `stores and loads entry`() {\n        KeychainCacheStore.setTestStoreForTesting(true)\n        defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n        let key = KeychainCacheStore.Key(category: \"test\", identifier: UUID().uuidString)\n        let storedAt = Date(timeIntervalSince1970: 0)\n        let entry = TestEntry(value: \"alpha\", storedAt: storedAt)\n\n        KeychainCacheStore.store(key: key, entry: entry)\n        defer { KeychainCacheStore.clear(key: key) }\n\n        switch KeychainCacheStore.load(key: key, as: TestEntry.self) {\n        case let .found(loaded):\n            #expect(loaded == entry)\n        case .missing, .invalid:\n            #expect(Bool(false), \"Expected keychain cache entry\")\n        }\n    }\n\n    @Test\n    func `overwrites existing entry`() {\n        KeychainCacheStore.setTestStoreForTesting(true)\n        defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n        let key = KeychainCacheStore.Key(category: \"test\", identifier: UUID().uuidString)\n        let first = TestEntry(value: \"first\", storedAt: Date(timeIntervalSince1970: 1))\n        let second = TestEntry(value: \"second\", storedAt: Date(timeIntervalSince1970: 2))\n\n        KeychainCacheStore.store(key: key, entry: first)\n        KeychainCacheStore.store(key: key, entry: second)\n        defer { KeychainCacheStore.clear(key: key) }\n\n        switch KeychainCacheStore.load(key: key, as: TestEntry.self) {\n        case let .found(loaded):\n            #expect(loaded == second)\n        case .missing, .invalid:\n            #expect(Bool(false), \"Expected overwritten keychain cache entry\")\n        }\n    }\n\n    @Test\n    func `clear removes entry`() {\n        KeychainCacheStore.setTestStoreForTesting(true)\n        defer { KeychainCacheStore.setTestStoreForTesting(false) }\n\n        let key = KeychainCacheStore.Key(category: \"test\", identifier: UUID().uuidString)\n        let entry = TestEntry(value: \"gone\", storedAt: Date(timeIntervalSince1970: 0))\n\n        KeychainCacheStore.store(key: key, entry: entry)\n        KeychainCacheStore.clear(key: key)\n\n        switch KeychainCacheStore.load(key: key, as: TestEntry.self) {\n        case .missing:\n            #expect(true)\n        case .found, .invalid:\n            #expect(Bool(false), \"Expected keychain cache entry to be cleared\")\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/KeychainMigrationTests.swift",
    "content": "import Testing\n@testable import CodexBar\n\nstruct KeychainMigrationTests {\n    @Test\n    func `migration list covers known keychain items`() {\n        let items = Set(KeychainMigration.itemsToMigrate.map(\\.label))\n        let expected: Set = [\n            \"com.steipete.CodexBar:codex-cookie\",\n            \"com.steipete.CodexBar:claude-cookie\",\n            \"com.steipete.CodexBar:cursor-cookie\",\n            \"com.steipete.CodexBar:factory-cookie\",\n            \"com.steipete.CodexBar:minimax-cookie\",\n            \"com.steipete.CodexBar:minimax-api-token\",\n            \"com.steipete.CodexBar:augment-cookie\",\n            \"com.steipete.CodexBar:copilot-api-token\",\n            \"com.steipete.CodexBar:zai-api-token\",\n            \"com.steipete.CodexBar:synthetic-api-key\",\n        ]\n\n        let missing = expected.subtracting(items)\n        #expect(missing.isEmpty, \"Missing migration entries: \\(missing.sorted())\")\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/KiloSettingsReaderTests.swift",
    "content": "import CodexBarCore\nimport Testing\n\nstruct KiloSettingsReaderTests {\n    @Test\n    func `api URL defaults to app kilo AI trpc`() {\n        let url = KiloSettingsReader.apiURL(environment: [:])\n\n        #expect(url.scheme == \"https\")\n        #expect(url.host() == \"app.kilo.ai\")\n        #expect(url.path == \"/api/trpc\")\n    }\n\n    @Test\n    func `api URL ignores environment override`() {\n        let url = KiloSettingsReader.apiURL(environment: [\"KILO_API_URL\": \"https://proxy.example.com/trpc\"])\n\n        #expect(url.host() == \"app.kilo.ai\")\n        #expect(url.path == \"/api/trpc\")\n    }\n\n    @Test\n    func `descriptor uses app kilo AI dashboard`() {\n        let descriptor = ProviderDescriptorRegistry.descriptor(for: .kilo)\n        #expect(descriptor.metadata.dashboardURL == \"https://app.kilo.ai/usage\")\n    }\n\n    @Test\n    func `descriptor uses dedicated kilo icon resource`() {\n        let descriptor = ProviderDescriptorRegistry.descriptor(for: .kilo)\n        #expect(descriptor.branding.iconResourceName == \"ProviderIcon-kilo\")\n    }\n\n    @Test\n    func `descriptor supports auto API and CLI source modes`() {\n        let descriptor = ProviderDescriptorRegistry.descriptor(for: .kilo)\n        let expected: Set<ProviderSourceMode> = [.auto, .api, .cli]\n        #expect(descriptor.fetchPlan.sourceModes == expected)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/KiloUsageFetcherTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\nstruct KiloUsageFetcherTests {\n    private struct StubClaudeFetcher: ClaudeUsageFetching {\n        func loadLatestUsage(model _: String) async throws -> ClaudeUsageSnapshot {\n            throw ClaudeUsageError.parseFailed(\"stub\")\n        }\n\n        func debugRawProbe(model _: String) async -> String {\n            \"stub\"\n        }\n\n        func detectVersion() -> String? {\n            nil\n        }\n    }\n\n    private func makeContext(\n        env: [String: String] = [:],\n        sourceMode: ProviderSourceMode = .api) -> ProviderFetchContext\n    {\n        let browserDetection = BrowserDetection(cacheTTL: 0)\n        return ProviderFetchContext(\n            runtime: .cli,\n            sourceMode: sourceMode,\n            includeCredits: false,\n            webTimeout: 1,\n            webDebugDumpHTML: false,\n            verbose: false,\n            env: env,\n            settings: nil,\n            fetcher: UsageFetcher(environment: env),\n            claudeFetcher: StubClaudeFetcher(),\n            browserDetection: browserDetection)\n    }\n\n    @Test\n    func `batch URL uses authenticated TRPC batch format`() throws {\n        let baseURL = try #require(URL(string: \"https://kilo.example/trpc\"))\n        let url = try KiloUsageFetcher._buildBatchURLForTesting(baseURL: baseURL)\n\n        #expect(url.path.contains(\"user.getCreditBlocks,kiloPass.getState,user.getAutoTopUpPaymentMethod\"))\n\n        let components = try #require(URLComponents(url: url, resolvingAgainstBaseURL: false))\n        let batch = components.queryItems?.first(where: { $0.name == \"batch\" })?.value\n        let inputValue = components.queryItems?.first(where: { $0.name == \"input\" })?.value\n\n        #expect(batch == \"1\")\n        let requiredInput = try #require(inputValue)\n        let inputData = Data(requiredInput.utf8)\n        let inputObject = try #require(try JSONSerialization.jsonObject(with: inputData) as? [String: Any])\n        let first = try #require(inputObject[\"0\"] as? [String: Any])\n        let second = try #require(inputObject[\"1\"] as? [String: Any])\n        let third = try #require(inputObject[\"2\"] as? [String: Any])\n\n        #expect(inputObject.keys.contains(\"0\"))\n        #expect(inputObject.keys.contains(\"1\"))\n        #expect(inputObject.keys.contains(\"2\"))\n        #expect(first[\"json\"] is NSNull)\n        #expect(second[\"json\"] is NSNull)\n        #expect(third[\"json\"] is NSNull)\n    }\n\n    @Test\n    func `parse snapshot maps business fields and identity`() throws {\n        let json = \"\"\"\n        [\n          {\n            \"result\": {\n              \"data\": {\n                \"json\": {\n                  \"blocks\": [\n                    {\n                      \"usedCredits\": 25,\n                      \"totalCredits\": 100,\n                      \"remainingCredits\": 75\n                    }\n                  ]\n                }\n              }\n            }\n          },\n          {\n            \"result\": {\n              \"data\": {\n                \"json\": {\n                  \"plan\": {\n                    \"name\": \"Kilo Pass Pro\"\n                  }\n                }\n              }\n            }\n          },\n          {\n            \"result\": {\n              \"data\": {\n                \"json\": {\n                  \"enabled\": true,\n                  \"paymentMethod\": \"visa\"\n                }\n              }\n            }\n          }\n        ]\n        \"\"\"\n\n        let parsed = try KiloUsageFetcher._parseSnapshotForTesting(Data(json.utf8))\n        let snapshot = parsed.toUsageSnapshot()\n\n        #expect(snapshot.primary?.usedPercent == 25)\n        #expect(snapshot.identity?.providerID == .kilo)\n        #expect(snapshot.loginMethod(for: .kilo)?.contains(\"Kilo Pass Pro\") == true)\n        #expect(snapshot.loginMethod(for: .kilo)?.contains(\"Auto top-up\") == true)\n    }\n\n    @Test\n    func `parse snapshot maps kilo pass window from subscription state`() throws {\n        let json = \"\"\"\n        [\n          {\n            \"result\": {\n              \"data\": {\n                \"creditBlocks\": [\n                  {\n                    \"id\": \"cb-1\",\n                    \"effective_date\": \"2026-02-01T00:00:00Z\",\n                    \"expiry_date\": null,\n                    \"balance_mUsd\": 19000000,\n                    \"amount_mUsd\": 19000000,\n                    \"is_free\": false\n                  }\n                ],\n                \"totalBalance_mUsd\": 19000000,\n                \"autoTopUpEnabled\": false\n              }\n            }\n          },\n          {\n            \"result\": {\n              \"data\": {\n                \"subscription\": {\n                  \"tier\": \"tier_19\",\n                  \"currentPeriodUsageUsd\": 0,\n                  \"currentPeriodBaseCreditsUsd\": 19.0,\n                  \"currentPeriodBonusCreditsUsd\": 9.5,\n                  \"nextBillingAt\": \"2026-03-28T04:00:00.000Z\"\n                }\n              }\n            }\n          },\n          {\n            \"result\": {\n              \"data\": {\n                \"enabled\": false,\n                \"amountCents\": 5000,\n                \"paymentMethod\": null\n              }\n            }\n          }\n        ]\n        \"\"\"\n\n        let parsed = try KiloUsageFetcher._parseSnapshotForTesting(Data(json.utf8))\n        let snapshot = parsed.toUsageSnapshot()\n\n        #expect(snapshot.primary?.usedPercent == 0)\n        #expect(snapshot.secondary?.usedPercent == 0)\n        #expect(snapshot.secondary?.resetsAt != nil)\n        #expect(snapshot.secondary?.resetDescription == \"$0.00 / $19.00 (+ $9.50 bonus)\")\n        #expect(snapshot.loginMethod(for: .kilo) == \"Starter · Auto top-up: off\")\n    }\n\n    @Test\n    func `parse snapshot maps known tier names and defaults to kilo pass`() throws {\n        let proTierJSON = \"\"\"\n        [\n          { \"result\": { \"data\": { \"creditBlocks\": [], \"totalBalance_mUsd\": 0, \"autoTopUpEnabled\": false } } },\n          { \"result\": { \"data\": { \"subscription\": { \"tier\": \"tier_49\" } } } },\n          { \"result\": { \"data\": { \"enabled\": false, \"paymentMethod\": null } } }\n        ]\n        \"\"\"\n        let proTierSnapshot = try KiloUsageFetcher._parseSnapshotForTesting(Data(proTierJSON.utf8)).toUsageSnapshot()\n        #expect(proTierSnapshot.loginMethod(for: .kilo) == \"Pro · Auto top-up: off\")\n\n        let noTierJSON = \"\"\"\n        [\n          { \"result\": { \"data\": { \"creditBlocks\": [], \"totalBalance_mUsd\": 0, \"autoTopUpEnabled\": false } } },\n          { \"result\": { \"data\": { \"subscription\": {\n            \"currentPeriodUsageUsd\": 1.0,\n            \"currentPeriodBaseCreditsUsd\": 19.0\n          } } } },\n          { \"result\": { \"data\": { \"enabled\": false, \"paymentMethod\": null } } }\n        ]\n        \"\"\"\n        let noTierSnapshot = try KiloUsageFetcher._parseSnapshotForTesting(Data(noTierJSON.utf8)).toUsageSnapshot()\n        #expect(noTierSnapshot.loginMethod(for: .kilo) == \"Kilo Pass · Auto top-up: off\")\n    }\n\n    @Test\n    func `parse snapshot uses auto top up amount when enabled without payment method`() throws {\n        let json = \"\"\"\n        [\n          { \"result\": { \"data\": { \"creditBlocks\": [], \"totalBalance_mUsd\": 0, \"autoTopUpEnabled\": true } } },\n          { \"result\": { \"data\": { \"subscription\": null } } },\n          { \"result\": { \"data\": { \"enabled\": true, \"amountCents\": 5000, \"paymentMethod\": null } } }\n        ]\n        \"\"\"\n\n        let snapshot = try KiloUsageFetcher._parseSnapshotForTesting(Data(json.utf8)).toUsageSnapshot()\n        #expect(snapshot.loginMethod(for: .kilo) == \"Auto top-up: $50\")\n    }\n\n    @Test\n    func `parse snapshot fallback pass fields use micro dollar scale`() throws {\n        let json = \"\"\"\n        [\n          {\n            \"result\": {\n              \"data\": {\n                \"json\": {\n                  \"blocks\": [\n                    {\n                      \"usedCredits\": 0,\n                      \"totalCredits\": 19,\n                      \"remainingCredits\": 19\n                    }\n                  ]\n                }\n              }\n            }\n          },\n          {\n            \"result\": {\n              \"data\": {\n                \"json\": {\n                  \"planName\": \"Starter\",\n                  \"amount_mUsd\": 28500000,\n                  \"used_mUsd\": 3500000,\n                  \"bonus_mUsd\": 9500000,\n                  \"nextRenewalAt\": \"2026-03-28T04:00:00.000Z\"\n                }\n              }\n            }\n          },\n          {\n            \"result\": {\n              \"data\": {\n                \"json\": {\n                  \"enabled\": false,\n                  \"paymentMethod\": null\n                }\n              }\n            }\n          }\n        ]\n        \"\"\"\n\n        let snapshot = try KiloUsageFetcher._parseSnapshotForTesting(Data(json.utf8)).toUsageSnapshot()\n        #expect(snapshot.secondary?.resetDescription == \"$3.50 / $19.00 (+ $9.50 bonus)\")\n        #expect(snapshot.loginMethod(for: .kilo) == \"Starter · Auto top-up: off\")\n    }\n\n    @Test\n    func `parse snapshot treats empty and null business fields as no data success`() throws {\n        let json = \"\"\"\n        [\n          {\n            \"result\": {\n              \"data\": {\n                \"json\": {\n                  \"blocks\": []\n                }\n              }\n            }\n          },\n          {\n            \"result\": {\n              \"data\": {\n                \"json\": {\n                  \"plan\": {\n                    \"name\": null\n                  }\n                }\n              }\n            }\n          },\n          {\n            \"result\": {\n              \"data\": {\n                \"json\": {\n                  \"enabled\": null,\n                  \"paymentMethod\": null\n                }\n              }\n            }\n          }\n        ]\n        \"\"\"\n\n        let parsed = try KiloUsageFetcher._parseSnapshotForTesting(Data(json.utf8))\n        let snapshot = parsed.toUsageSnapshot()\n\n        #expect(snapshot.primary == nil)\n        #expect(snapshot.identity?.providerID == .kilo)\n        #expect(snapshot.loginMethod(for: .kilo) == nil)\n    }\n\n    @Test\n    func `parse snapshot keeps sparse indexed object routing by procedure index`() throws {\n        let json = \"\"\"\n        {\n          \"0\": {\n            \"result\": {\n              \"data\": {\n                \"json\": {\n                  \"creditsUsed\": 10,\n                  \"creditsRemaining\": 90\n                }\n              }\n            }\n          },\n          \"2\": {\n            \"result\": {\n              \"data\": {\n                \"json\": {\n                  \"planName\": \"wrong-route\",\n                  \"enabled\": true,\n                  \"method\": \"visa\"\n                }\n              }\n            }\n          }\n        }\n        \"\"\"\n\n        let parsed = try KiloUsageFetcher._parseSnapshotForTesting(Data(json.utf8))\n        let snapshot = parsed.toUsageSnapshot()\n\n        #expect(snapshot.primary?.usedPercent == 10)\n        #expect(snapshot.loginMethod(for: .kilo) == \"Auto top-up: visa\")\n    }\n\n    @Test\n    func `parse snapshot uses top level credits used fallback`() throws {\n        let json = \"\"\"\n        [\n          {\n            \"result\": {\n              \"data\": {\n                \"json\": {\n                  \"creditsUsed\": 40,\n                  \"creditsRemaining\": 60\n                }\n              }\n            }\n          }\n        ]\n        \"\"\"\n\n        let parsed = try KiloUsageFetcher._parseSnapshotForTesting(Data(json.utf8))\n        let snapshot = parsed.toUsageSnapshot()\n\n        #expect(snapshot.primary?.usedPercent == 40)\n        #expect(snapshot.primary?.resetDescription == \"40/100 credits\")\n    }\n\n    @Test\n    func `parse snapshot keeps zero total visible when activity exists`() throws {\n        let json = \"\"\"\n        [\n          {\n            \"result\": {\n              \"data\": {\n                \"json\": {\n                  \"creditsUsed\": 0,\n                  \"creditsRemaining\": 0\n                }\n              }\n            }\n          },\n          {\n            \"result\": {\n              \"data\": {\n                \"json\": {\n                  \"planName\": \"Kilo Pass Pro\"\n                }\n              }\n            }\n          },\n          {\n            \"result\": {\n              \"data\": {\n                \"json\": {\n                  \"enabled\": true,\n                  \"paymentMethod\": \"visa\"\n                }\n              }\n            }\n          }\n        ]\n        \"\"\"\n\n        let parsed = try KiloUsageFetcher._parseSnapshotForTesting(Data(json.utf8))\n        let snapshot = parsed.toUsageSnapshot()\n\n        #expect(snapshot.primary?.remainingPercent == 0)\n        #expect(snapshot.primary?.usedPercent == 100)\n        #expect(snapshot.primary?.resetDescription == \"0/0 credits\")\n        #expect(snapshot.loginMethod(for: .kilo)?.contains(\"Auto top-up: visa\") == true)\n    }\n\n    @Test\n    func `parse snapshot treats zero balance without credit blocks as visible zero total`() throws {\n        let json = \"\"\"\n        [\n          {\n            \"result\": {\n              \"data\": {\n                \"creditBlocks\": [],\n                \"totalBalance_mUsd\": 0,\n                \"isFirstPurchase\": true,\n                \"autoTopUpEnabled\": false\n              }\n            }\n          },\n          {\n            \"result\": {\n              \"data\": {\n                \"subscription\": null\n              }\n            }\n          },\n          {\n            \"result\": {\n              \"data\": {\n                \"enabled\": false,\n                \"amountCents\": 5000,\n                \"paymentMethod\": null\n              }\n            }\n          }\n        ]\n        \"\"\"\n\n        let parsed = try KiloUsageFetcher._parseSnapshotForTesting(Data(json.utf8))\n        let snapshot = parsed.toUsageSnapshot()\n\n        #expect(snapshot.primary?.usedPercent == 100)\n        #expect(snapshot.primary?.remainingPercent == 0)\n        #expect(snapshot.primary?.resetDescription == \"0/0 credits\")\n        #expect(snapshot.loginMethod(for: .kilo) == \"Auto top-up: off\")\n    }\n\n    @Test\n    func `parse snapshot degrades optional auto top up TRPC error`() throws {\n        let json = \"\"\"\n        [\n          {\n            \"result\": {\n              \"data\": {\n                \"json\": {\n                  \"creditsUsed\": 10,\n                  \"creditsRemaining\": 90\n                }\n              }\n            }\n          },\n          {\n            \"result\": {\n              \"data\": {\n                \"json\": {\n                  \"planName\": \"Starter\"\n                }\n              }\n            }\n          },\n          {\n            \"error\": {\n              \"json\": {\n                \"message\": \"Internal server error\",\n                \"data\": {\n                  \"code\": \"INTERNAL_SERVER_ERROR\"\n                }\n              }\n            }\n          }\n        ]\n        \"\"\"\n\n        let parsed = try KiloUsageFetcher._parseSnapshotForTesting(Data(json.utf8))\n        let snapshot = parsed.toUsageSnapshot()\n\n        #expect(snapshot.primary?.usedPercent == 10)\n        #expect(snapshot.loginMethod(for: .kilo) == \"Starter\")\n    }\n\n    @Test\n    func `parse snapshot keeps required procedure TRPC error fatal`() {\n        let json = \"\"\"\n        [\n          {\n            \"result\": {\n              \"data\": {\n                \"json\": {\n                  \"creditsUsed\": 10,\n                  \"creditsRemaining\": 90\n                }\n              }\n            }\n          },\n          {\n            \"error\": {\n              \"json\": {\n                \"message\": \"Unauthorized\",\n                \"data\": {\n                  \"code\": \"UNAUTHORIZED\"\n                }\n              }\n            }\n          }\n        ]\n        \"\"\"\n\n        #expect {\n            _ = try KiloUsageFetcher._parseSnapshotForTesting(Data(json.utf8))\n        } throws: { error in\n            guard let kiloError = error as? KiloUsageError else { return false }\n            guard case .unauthorized = kiloError else { return false }\n            return true\n        }\n    }\n\n    @Test\n    func `parse snapshot maps unauthorized TRPC error`() {\n        let json = \"\"\"\n        [\n          {\n            \"error\": {\n              \"json\": {\n                \"message\": \"Unauthorized\",\n                \"data\": {\n                  \"code\": \"UNAUTHORIZED\"\n                }\n              }\n            }\n          }\n        ]\n        \"\"\"\n\n        #expect {\n            _ = try KiloUsageFetcher._parseSnapshotForTesting(Data(json.utf8))\n        } throws: { error in\n            guard let kiloError = error as? KiloUsageError else { return false }\n            guard case .unauthorized = kiloError else { return false }\n            return true\n        }\n    }\n\n    @Test\n    func `parse snapshot maps invalid JSON to parse error`() {\n        #expect {\n            _ = try KiloUsageFetcher._parseSnapshotForTesting(Data(\"not-json\".utf8))\n        } throws: { error in\n            guard let kiloError = error as? KiloUsageError else { return false }\n            guard case .parseFailed = kiloError else { return false }\n            return true\n        }\n    }\n\n    @Test\n    func `status error mapping covers auth and server failures`() {\n        #expect(KiloUsageFetcher._statusErrorForTesting(401) == .unauthorized)\n        #expect(KiloUsageFetcher._statusErrorForTesting(403) == .unauthorized)\n        #expect(KiloUsageFetcher._statusErrorForTesting(404) == .endpointNotFound)\n\n        guard let serviceError = KiloUsageFetcher._statusErrorForTesting(503) else {\n            Issue.record(\"Expected service unavailable mapping\")\n            return\n        }\n        guard case let .serviceUnavailable(statusCode) = serviceError else {\n            Issue.record(\"Expected service unavailable mapping\")\n            return\n        }\n        #expect(statusCode == 503)\n    }\n\n    @Test\n    func `fetch usage without credentials fails fast`() async {\n        await #expect(throws: KiloUsageError.missingCredentials) {\n            _ = try await KiloUsageFetcher.fetchUsage(apiKey: \"  \", environment: [:])\n        }\n    }\n\n    @Test\n    func `descriptor fetch outcome without credentials returns actionable error`() async {\n        let descriptor = ProviderDescriptorRegistry.descriptor(for: .kilo)\n        let outcome = await descriptor.fetchOutcome(context: self.makeContext())\n\n        switch outcome.result {\n        case .success:\n            Issue.record(\"Expected missing credentials failure\")\n        case let .failure(error):\n            #expect((error as? KiloUsageError) == .missingCredentials)\n        }\n\n        #expect(outcome.attempts.count == 1)\n        #expect(outcome.attempts.first?.strategyID == \"kilo.api\")\n        #expect(outcome.attempts.first?.wasAvailable == true)\n    }\n\n    @Test\n    func `descriptor API mode ignores CLI session fallback`() async throws {\n        let homeDirectory = try self.makeTemporaryHomeDirectory()\n        defer { try? FileManager.default.removeItem(at: homeDirectory) }\n        try self.writeKiloAuthFile(\n            homeDirectory: homeDirectory,\n            contents: #\"{\"kilo\":{\"access\":\"file-token\"}}\"#)\n\n        let descriptor = ProviderDescriptorRegistry.descriptor(for: .kilo)\n        let outcome = await descriptor.fetchOutcome(context: self.makeContext(\n            env: [\"HOME\": homeDirectory.path],\n            sourceMode: .api))\n\n        switch outcome.result {\n        case .success:\n            Issue.record(\"Expected missing API credentials failure\")\n        case let .failure(error):\n            #expect((error as? KiloUsageError) == .missingCredentials)\n        }\n\n        #expect(outcome.attempts.count == 1)\n        #expect(outcome.attempts.first?.strategyID == \"kilo.api\")\n    }\n\n    @Test\n    func `descriptor CLI mode missing session returns actionable error`() async throws {\n        let homeDirectory = try self.makeTemporaryHomeDirectory()\n        defer { try? FileManager.default.removeItem(at: homeDirectory) }\n        let expectedPath = KiloSettingsReader.defaultAuthFileURL(homeDirectory: homeDirectory).path\n\n        let descriptor = ProviderDescriptorRegistry.descriptor(for: .kilo)\n        let outcome = await descriptor.fetchOutcome(context: self.makeContext(\n            env: [\"HOME\": homeDirectory.path],\n            sourceMode: .cli))\n\n        switch outcome.result {\n        case .success:\n            Issue.record(\"Expected missing CLI session failure\")\n        case let .failure(error):\n            #expect((error as? KiloUsageError) == .cliSessionMissing(expectedPath))\n        }\n\n        #expect(outcome.attempts.count == 1)\n        #expect(outcome.attempts.first?.strategyID == \"kilo.cli\")\n    }\n\n    @Test\n    func `descriptor auto mode falls back from API to CLI`() async throws {\n        let homeDirectory = try self.makeTemporaryHomeDirectory()\n        defer { try? FileManager.default.removeItem(at: homeDirectory) }\n        let expectedPath = KiloSettingsReader.defaultAuthFileURL(homeDirectory: homeDirectory).path\n\n        let descriptor = ProviderDescriptorRegistry.descriptor(for: .kilo)\n        let outcome = await descriptor.fetchOutcome(context: self.makeContext(\n            env: [\"HOME\": homeDirectory.path],\n            sourceMode: .auto))\n\n        switch outcome.result {\n        case .success:\n            Issue.record(\"Expected missing CLI session failure after API fallback\")\n        case let .failure(error):\n            #expect((error as? KiloUsageError) == .cliSessionMissing(expectedPath))\n        }\n\n        #expect(outcome.attempts.count == 2)\n        #expect(outcome.attempts.map(\\.strategyID) == [\"kilo.api\", \"kilo.cli\"])\n    }\n\n    @Test\n    func `api strategy falls back on unauthorized only in auto mode`() {\n        let strategy = KiloAPIFetchStrategy()\n        #expect(strategy.shouldFallback(\n            on: KiloUsageError.unauthorized,\n            context: self.makeContext(sourceMode: .auto)))\n        #expect(!strategy.shouldFallback(\n            on: KiloUsageError.unauthorized,\n            context: self.makeContext(sourceMode: .api)))\n    }\n\n    @Test\n    func `api strategy falls back on missing credentials only in auto mode`() {\n        let strategy = KiloAPIFetchStrategy()\n        #expect(strategy.shouldFallback(\n            on: KiloUsageError.missingCredentials,\n            context: self.makeContext(sourceMode: .auto)))\n        #expect(!strategy.shouldFallback(\n            on: KiloUsageError.missingCredentials,\n            context: self.makeContext(sourceMode: .api)))\n    }\n\n    private func makeTemporaryHomeDirectory() throws -> URL {\n        let directory = FileManager.default.temporaryDirectory\n            .appendingPathComponent(UUID().uuidString, isDirectory: true)\n        try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)\n        return directory\n    }\n\n    private func writeKiloAuthFile(homeDirectory: URL, contents: String) throws {\n        let fileURL = KiloSettingsReader.defaultAuthFileURL(homeDirectory: homeDirectory)\n        try FileManager.default.createDirectory(\n            at: fileURL.deletingLastPathComponent(),\n            withIntermediateDirectories: true)\n        try contents.write(to: fileURL, atomically: true, encoding: .utf8)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/KimiK2SettingsReaderTests.swift",
    "content": "import CodexBarCore\nimport Testing\n\nstruct KimiK2SettingsReaderTests {\n    @Test\n    func `api key is trimmed`() {\n        let env = [\"KIMI_API_KEY\": \"  key-123  \"]\n        #expect(KimiK2SettingsReader.apiKey(environment: env) == \"key-123\")\n    }\n\n    @Test\n    func `api key strips quotes`() {\n        let env = [\"KIMI_KEY\": \"\\\"quoted-456\\\"\"]\n        #expect(KimiK2SettingsReader.apiKey(environment: env) == \"quoted-456\")\n    }\n}\n\nstruct KimiK2ProviderTokenResolverTests {\n    @Test\n    func `resolves from environment`() {\n        let env = [\"KIMI_API_KEY\": \"env-token\"]\n        let resolution = ProviderTokenResolver.kimiK2Resolution(environment: env)\n        #expect(resolution?.token == \"env-token\")\n        #expect(resolution?.source == .environment)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/KimiK2TokenStoreTestSupport.swift",
    "content": "@testable import CodexBar\n\nstruct NoopKimiK2TokenStore: KimiK2TokenStoring {\n    func loadToken() throws -> String? {\n        nil\n    }\n\n    func storeToken(_: String?) throws {}\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/KimiK2UsageFetcherTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\nstruct KimiK2UsageFetcherTests {\n    @Test\n    func `parses usage from nested usage`() throws {\n        let json = \"\"\"\n        {\n          \"data\": {\n            \"usage\": {\n              \"total\": 120,\n              \"credits_remaining\": 30,\n              \"average_tokens\": 42,\n              \"updated_at\": \"2024-01-02T03:04:05Z\"\n            }\n          }\n        }\n        \"\"\"\n\n        let summary = try KimiK2UsageFetcher._parseSummaryForTesting(Data(json.utf8))\n        let expectedDate = Date(timeIntervalSince1970: 1_704_164_645)\n\n        #expect(summary.consumed == 120)\n        #expect(summary.remaining == 30)\n        #expect(summary.averageTokens == 42)\n        #expect(abs(summary.updatedAt.timeIntervalSince1970 - expectedDate.timeIntervalSince1970) < 0.5)\n    }\n\n    @Test\n    func `uses header fallback for remaining credits`() throws {\n        let json = \"\"\"\n        { \"total_credits_consumed\": 50 }\n        \"\"\"\n        let headers: [AnyHashable: Any] = [\"X-Credits-Remaining\": \"25\"]\n\n        let summary = try KimiK2UsageFetcher._parseSummaryForTesting(Data(json.utf8), headers: headers)\n\n        #expect(summary.consumed == 50)\n        #expect(summary.remaining == 25)\n    }\n\n    @Test\n    func `parses numeric timestamp seconds`() throws {\n        let json = \"\"\"\n        {\n          \"timestamp\": 1700000000,\n          \"credits_remaining\": 10,\n          \"total_credits_consumed\": 5\n        }\n        \"\"\"\n\n        let summary = try KimiK2UsageFetcher._parseSummaryForTesting(Data(json.utf8))\n        let expected = Date(timeIntervalSince1970: 1_700_000_000)\n\n        #expect(abs(summary.updatedAt.timeIntervalSince1970 - expected.timeIntervalSince1970) < 0.5)\n    }\n\n    @Test\n    func `parses numeric timestamp milliseconds`() throws {\n        let json = \"\"\"\n        {\n          \"timestamp\": 1700000000000,\n          \"credits_remaining\": 10,\n          \"total_credits_consumed\": 5\n        }\n        \"\"\"\n\n        let summary = try KimiK2UsageFetcher._parseSummaryForTesting(Data(json.utf8))\n        let expected = Date(timeIntervalSince1970: 1_700_000_000)\n\n        #expect(abs(summary.updatedAt.timeIntervalSince1970 - expected.timeIntervalSince1970) < 0.5)\n    }\n\n    @Test\n    func `invalid root returns parse error`() {\n        let json = \"\"\"\n        [{ \"total\": 1 }]\n        \"\"\"\n\n        #expect {\n            _ = try KimiK2UsageFetcher._parseSummaryForTesting(Data(json.utf8))\n        } throws: { error in\n            guard case let KimiK2UsageError.parseFailed(message) = error else { return false }\n            return message == \"Root JSON is not an object.\"\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/KimiProviderTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\nstruct KimiSettingsReaderTests {\n    @Test\n    func `reads token from environment variable`() {\n        let env = [\"KIMI_AUTH_TOKEN\": \"test.jwt.token\"]\n        let token = KimiSettingsReader.authToken(environment: env)\n        #expect(token == \"test.jwt.token\")\n    }\n\n    @Test\n    func `normalizes quoted token`() {\n        let env = [\"KIMI_AUTH_TOKEN\": \"\\\"test.jwt.token\\\"\"]\n        let token = KimiSettingsReader.authToken(environment: env)\n        #expect(token == \"test.jwt.token\")\n    }\n\n    @Test\n    func `returns nil when missing`() {\n        let env: [String: String] = [:]\n        let token = KimiSettingsReader.authToken(environment: env)\n        #expect(token == nil)\n    }\n\n    @Test\n    func `returns nil when empty`() {\n        let env = [\"KIMI_AUTH_TOKEN\": \"\"]\n        let token = KimiSettingsReader.authToken(environment: env)\n        #expect(token == nil)\n    }\n\n    @Test\n    func `normalizes lowercase environment key`() {\n        let env = [\"kimi_auth_token\": \"test.jwt.token\"]\n        let token = KimiSettingsReader.authToken(environment: env)\n        #expect(token == \"test.jwt.token\")\n    }\n}\n\nstruct KimiUsageResponseParsingTests {\n    @Test\n    func `parses valid response`() throws {\n        let json = \"\"\"\n        {\n          \"usages\": [\n            {\n              \"scope\": \"FEATURE_CODING\",\n              \"detail\": {\n                \"limit\": \"2048\",\n                \"used\": \"375\",\n                \"remaining\": \"1673\",\n                \"resetTime\": \"2026-01-09T15:23:13.373329235Z\"\n              },\n              \"limits\": [\n                {\n                  \"window\": {\n                    \"duration\": 300,\n                    \"timeUnit\": \"TIME_UNIT_MINUTE\"\n                  },\n                  \"detail\": {\n                    \"limit\": \"200\",\n                    \"used\": \"200\",\n                    \"resetTime\": \"2026-01-06T15:05:24.374187075Z\"\n                  }\n                }\n              ]\n            }\n          ]\n        }\n        \"\"\"\n\n        let response = try JSONDecoder().decode(KimiUsageResponse.self, from: Data(json.utf8))\n\n        #expect(response.usages.count == 1)\n        let usage = response.usages[0]\n        #expect(usage.scope == \"FEATURE_CODING\")\n        #expect(usage.detail.limit == \"2048\")\n        #expect(usage.detail.used == \"375\")\n        #expect(usage.detail.remaining == \"1673\")\n        #expect(usage.detail.resetTime == \"2026-01-09T15:23:13.373329235Z\")\n\n        #expect(usage.limits?.count == 1)\n        let rateLimit = usage.limits?.first\n        #expect(rateLimit?.window.duration == 300)\n        #expect(rateLimit?.window.timeUnit == \"TIME_UNIT_MINUTE\")\n        #expect(rateLimit?.detail.limit == \"200\")\n        #expect(rateLimit?.detail.used == \"200\")\n    }\n\n    @Test\n    func `parses response without rate limits`() throws {\n        let json = \"\"\"\n        {\n          \"usages\": [\n            {\n              \"scope\": \"FEATURE_CODING\",\n              \"detail\": {\n                \"limit\": \"2048\",\n                \"used\": \"375\",\n                \"remaining\": \"1673\",\n                \"resetTime\": \"2026-01-09T15:23:13.373329235Z\"\n              },\n              \"limits\": []\n            }\n          ]\n        }\n        \"\"\"\n\n        let response = try JSONDecoder().decode(KimiUsageResponse.self, from: Data(json.utf8))\n        #expect(response.usages.first?.limits?.isEmpty == true)\n    }\n\n    @Test\n    func `parses response with null limits`() throws {\n        let json = \"\"\"\n        {\n          \"usages\": [\n            {\n              \"scope\": \"FEATURE_CODING\",\n              \"detail\": {\n                \"limit\": \"2048\",\n                \"used\": \"375\",\n                \"remaining\": \"1673\",\n                \"resetTime\": \"2026-01-09T15:23:13.373329235Z\"\n              },\n              \"limits\": null\n            }\n          ]\n        }\n        \"\"\"\n\n        let response = try JSONDecoder().decode(KimiUsageResponse.self, from: Data(json.utf8))\n        #expect(response.usages.first?.limits == nil)\n    }\n\n    @Test\n    func `throws on invalid json`() {\n        let invalidJson = \"{ invalid json }\"\n\n        #expect(throws: DecodingError.self) {\n            try JSONDecoder().decode(KimiUsageResponse.self, from: Data(invalidJson.utf8))\n        }\n    }\n\n    @Test\n    func `throws on missing feature coding scope`() throws {\n        let json = \"\"\"\n        {\n          \"usages\": [\n            {\n              \"scope\": \"OTHER_SCOPE\",\n              \"detail\": {\n                \"limit\": \"100\",\n                \"used\": \"50\",\n                \"remaining\": \"50\",\n                \"resetTime\": \"2026-01-09T15:23:13.373329235Z\"\n              }\n            }\n          ]\n        }\n        \"\"\"\n\n        let response = try JSONDecoder().decode(KimiUsageResponse.self, from: Data(json.utf8))\n        let codingUsage = response.usages.first { $0.scope == \"FEATURE_CODING\" }\n        #expect(codingUsage == nil)\n    }\n}\n\nstruct KimiUsageSnapshotConversionTests {\n    @Test\n    func `converts to usage snapshot with both windows`() {\n        let now = Date()\n        let weeklyDetail = KimiUsageDetail(\n            limit: \"2048\",\n            used: \"375\",\n            remaining: \"1673\",\n            resetTime: \"2026-01-09T15:23:13.373329235Z\")\n        let rateLimitDetail = KimiUsageDetail(\n            limit: \"200\",\n            used: \"200\",\n            remaining: \"0\",\n            resetTime: \"2026-01-06T15:05:24.374187075Z\")\n\n        let snapshot = KimiUsageSnapshot(\n            weekly: weeklyDetail,\n            rateLimit: rateLimitDetail,\n            updatedAt: now)\n\n        let usageSnapshot = snapshot.toUsageSnapshot()\n\n        #expect(usageSnapshot.primary != nil)\n        let weeklyExpected = 375.0 / 2048.0 * 100.0\n        #expect(abs((usageSnapshot.primary?.usedPercent ?? 0.0) - weeklyExpected) < 0.01)\n        #expect(usageSnapshot.primary?.resetDescription == \"375/2048 requests\")\n        #expect(usageSnapshot.primary?.windowMinutes == nil)\n\n        #expect(usageSnapshot.secondary != nil)\n        let rateExpected = 200.0 / 200.0 * 100.0\n        #expect(abs((usageSnapshot.secondary?.usedPercent ?? 0.0) - rateExpected) < 0.01)\n        #expect(usageSnapshot.secondary?.windowMinutes == 300) // 5 hours\n        #expect(usageSnapshot.secondary?.resetDescription == \"Rate: 200/200 per 5 hours\")\n\n        #expect(usageSnapshot.tertiary == nil)\n        #expect(usageSnapshot.updatedAt == now)\n    }\n\n    @Test\n    func `converts to usage snapshot without rate limit`() {\n        let now = Date()\n        let weeklyDetail = KimiUsageDetail(\n            limit: \"2048\",\n            used: \"375\",\n            remaining: \"1673\",\n            resetTime: \"2026-01-09T15:23:13.373329235Z\")\n\n        let snapshot = KimiUsageSnapshot(\n            weekly: weeklyDetail,\n            rateLimit: nil,\n            updatedAt: now)\n\n        let usageSnapshot = snapshot.toUsageSnapshot()\n\n        #expect(usageSnapshot.primary != nil)\n        let weeklyExpected = 375.0 / 2048.0 * 100.0\n        #expect(abs((usageSnapshot.primary?.usedPercent ?? 0.0) - weeklyExpected) < 0.01)\n        #expect(usageSnapshot.secondary == nil)\n        #expect(usageSnapshot.tertiary == nil)\n    }\n\n    @Test\n    func `handles zero values correctly`() {\n        let now = Date()\n        let weeklyDetail = KimiUsageDetail(\n            limit: \"2048\",\n            used: \"0\",\n            remaining: \"2048\",\n            resetTime: \"2026-01-09T15:23:13.373329235Z\")\n\n        let snapshot = KimiUsageSnapshot(\n            weekly: weeklyDetail,\n            rateLimit: nil,\n            updatedAt: now)\n\n        let usageSnapshot = snapshot.toUsageSnapshot()\n        #expect(usageSnapshot.primary?.usedPercent == 0.0)\n    }\n\n    @Test\n    func `handles hundred percent correctly`() {\n        let now = Date()\n        let weeklyDetail = KimiUsageDetail(\n            limit: \"2048\",\n            used: \"2048\",\n            remaining: \"0\",\n            resetTime: \"2026-01-09T15:23:13.373329235Z\")\n\n        let snapshot = KimiUsageSnapshot(\n            weekly: weeklyDetail,\n            rateLimit: nil,\n            updatedAt: now)\n\n        let usageSnapshot = snapshot.toUsageSnapshot()\n        #expect(usageSnapshot.primary?.usedPercent == 100.0)\n    }\n}\n\nstruct KimiTokenResolverTests {\n    @Test\n    func `resolves token from environment`() {\n        KeychainAccessGate.withTaskOverrideForTesting(true) {\n            let env = [\"KIMI_AUTH_TOKEN\": \"test.jwt.token\"]\n            let token = ProviderTokenResolver.kimiAuthToken(environment: env)\n            #expect(token == \"test.jwt.token\")\n        }\n    }\n\n    @Test\n    func `resolves token from keychain first`() {\n        // This test would require mocking the keychain.\n        KeychainAccessGate.withTaskOverrideForTesting(true) {\n            let env = [\"KIMI_AUTH_TOKEN\": \"test.env.token\"]\n            let token = ProviderTokenResolver.kimiAuthToken(environment: env)\n            #expect(token == \"test.env.token\")\n        }\n    }\n\n    @Test\n    func `resolution includes source`() {\n        KeychainAccessGate.withTaskOverrideForTesting(true) {\n            let env = [\"KIMI_AUTH_TOKEN\": \"test.jwt.token\"]\n            let resolution = ProviderTokenResolver.kimiAuthResolution(environment: env)\n\n            #expect(resolution?.token == \"test.jwt.token\")\n            #expect(resolution?.source == .environment)\n        }\n    }\n}\n\nstruct KimiAPIErrorTests {\n    @Test\n    func `error descriptions are helpful`() {\n        #expect(KimiAPIError.missingToken.errorDescription?.contains(\"missing\") == true)\n        #expect(KimiAPIError.invalidToken.errorDescription?.contains(\"invalid\") == true)\n        #expect(KimiAPIError.invalidRequest(\"Bad request\").errorDescription?.contains(\"Bad request\") == true)\n        #expect(KimiAPIError.networkError(\"Timeout\").errorDescription?.contains(\"Timeout\") == true)\n        #expect(KimiAPIError.apiError(\"HTTP 500\").errorDescription?.contains(\"HTTP 500\") == true)\n        #expect(KimiAPIError.parseFailed(\"Invalid JSON\").errorDescription?.contains(\"Invalid JSON\") == true)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/KiroStatusProbeTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\nstruct KiroStatusProbeTests {\n    // MARK: - Happy Path Parsing\n\n    @Test\n    func `parses basic usage output`() throws {\n        let output = \"\"\"\n        | KIRO FREE                                          |\n        ████████████████████████████████████████████████████ 25%\n        (12.50 of 50 covered in plan), resets on 01/15\n        \"\"\"\n\n        let probe = KiroStatusProbe()\n        let snapshot = try probe.parse(output: output)\n\n        #expect(snapshot.planName == \"KIRO FREE\")\n        #expect(snapshot.creditsPercent == 25)\n        #expect(snapshot.creditsUsed == 12.50)\n        #expect(snapshot.creditsTotal == 50)\n        #expect(snapshot.bonusCreditsUsed == nil)\n        #expect(snapshot.bonusCreditsTotal == nil)\n        #expect(snapshot.bonusExpiryDays == nil)\n        #expect(snapshot.resetsAt != nil)\n    }\n\n    @Test\n    func `parses output with bonus credits`() throws {\n        let output = \"\"\"\n        | KIRO PRO                                           |\n        ████████████████████████████████████████████████████ 80%\n        (40.00 of 50 covered in plan), resets on 02/01\n        Bonus credits: 5.00/10 credits used, expires in 7 days\n        \"\"\"\n\n        let probe = KiroStatusProbe()\n        let snapshot = try probe.parse(output: output)\n\n        #expect(snapshot.planName == \"KIRO PRO\")\n        #expect(snapshot.creditsPercent == 80)\n        #expect(snapshot.creditsUsed == 40.00)\n        #expect(snapshot.creditsTotal == 50)\n        #expect(snapshot.bonusCreditsUsed == 5.00)\n        #expect(snapshot.bonusCreditsTotal == 10)\n        #expect(snapshot.bonusExpiryDays == 7)\n    }\n\n    @Test\n    func `parses output without percent fallbacks to credits ratio`() throws {\n        let output = \"\"\"\n        | KIRO FREE                                          |\n        (12.50 of 50 covered in plan), resets on 01/15\n        \"\"\"\n\n        let probe = KiroStatusProbe()\n        let snapshot = try probe.parse(output: output)\n\n        #expect(snapshot.creditsPercent == 25)\n    }\n\n    @Test\n    func `parses bonus credits without expiry`() throws {\n        let output = \"\"\"\n        | KIRO FREE                                          |\n        ████████████████████████████████████████████████████ 60%\n        (30.00 of 50 covered in plan), resets on 04/01\n        Bonus credits: 2.00/5 credits used\n        \"\"\"\n\n        let probe = KiroStatusProbe()\n        let snapshot = try probe.parse(output: output)\n\n        #expect(snapshot.bonusCreditsUsed == 2.0)\n        #expect(snapshot.bonusCreditsTotal == 5.0)\n        #expect(snapshot.bonusExpiryDays == nil)\n    }\n\n    @Test\n    func `parses output with ANSI codes`() throws {\n        let output = \"\"\"\n        \\u{001B}[32m| KIRO FREE                                          |\\u{001B}[0m\n        \\u{001B}[38;5;11m████████████████████████████████████████████████████\\u{001B}[0m 50%\n        (25.00 of 50 covered in plan), resets on 03/15\n        \"\"\"\n\n        let probe = KiroStatusProbe()\n        let snapshot = try probe.parse(output: output)\n\n        #expect(snapshot.planName == \"KIRO FREE\")\n        #expect(snapshot.creditsPercent == 50)\n        #expect(snapshot.creditsUsed == 25.00)\n        #expect(snapshot.creditsTotal == 50)\n    }\n\n    @Test\n    func `parses output with single day`() throws {\n        let output = \"\"\"\n        | KIRO FREE                                          |\n        ████████████████████████████████████████████████████ 10%\n        (5.00 of 50 covered in plan)\n        Bonus credits: 2.00/5 credits used, expires in 1 day\n        \"\"\"\n\n        let probe = KiroStatusProbe()\n        let snapshot = try probe.parse(output: output)\n\n        #expect(snapshot.bonusExpiryDays == 1)\n    }\n\n    @Test\n    func `rejects output missing usage markers`() throws {\n        let output = \"\"\"\n        | KIRO FREE                                          |\n        \"\"\"\n\n        let probe = KiroStatusProbe()\n        #expect(throws: KiroStatusProbeError.self) {\n            try probe.parse(output: output)\n        }\n    }\n\n    // MARK: - New Format (kiro-cli 1.24+, Q Developer)\n\n    @Test\n    func `parses Q developer managed plan`() throws {\n        let output = \"\"\"\n        Plan: Q Developer Pro\n        Your plan is managed by admin\n\n        Tip: to see context window usage, run /context\n        \"\"\"\n\n        let probe = KiroStatusProbe()\n        let snapshot = try probe.parse(output: output)\n\n        #expect(snapshot.planName == \"Q Developer Pro\")\n        #expect(snapshot.creditsPercent == 0)\n        #expect(snapshot.creditsUsed == 0)\n        #expect(snapshot.creditsTotal == 0)\n        #expect(snapshot.bonusCreditsUsed == nil)\n        #expect(snapshot.resetsAt == nil)\n    }\n\n    @Test\n    func `parses Q developer free plan`() throws {\n        let output = \"\"\"\n        Plan: Q Developer Free\n        Your plan is managed by admin\n        \"\"\"\n\n        let probe = KiroStatusProbe()\n        let snapshot = try probe.parse(output: output)\n\n        #expect(snapshot.planName == \"Q Developer Free\")\n        #expect(snapshot.creditsPercent == 0)\n    }\n\n    @Test\n    func `parses new format with ANSI codes`() throws {\n        let output = \"\"\"\n        \\u{001B}[38;5;141mPlan: Q Developer Pro\\u{001B}[0m\n        Your plan is managed by admin\n        \"\"\"\n\n        let probe = KiroStatusProbe()\n        let snapshot = try probe.parse(output: output)\n\n        #expect(snapshot.planName == \"Q Developer Pro\")\n    }\n\n    @Test\n    func `rejects header only new format without managed marker`() {\n        let output = \"\"\"\n        Plan: Q Developer Pro\n        Tip: to see context window usage, run /context\n        \"\"\"\n\n        let probe = KiroStatusProbe()\n        #expect(throws: KiroStatusProbeError.self) {\n            try probe.parse(output: output)\n        }\n    }\n\n    @Test\n    func `preserves parsed usage for managed plan with metrics`() throws {\n        let output = \"\"\"\n        Plan: Q Developer Enterprise\n        Your plan is managed by admin\n        ████████████████████████████████████████████████████ 40%\n        (20.00 of 50 covered in plan), resets on 03/15\n        \"\"\"\n\n        let probe = KiroStatusProbe()\n        let snapshot = try probe.parse(output: output)\n\n        #expect(snapshot.planName == \"Q Developer Enterprise\")\n        #expect(snapshot.creditsPercent == 40)\n        #expect(snapshot.creditsUsed == 20)\n        #expect(snapshot.creditsTotal == 50)\n        #expect(snapshot.resetsAt != nil)\n    }\n\n    // MARK: - Snapshot Conversion\n\n    @Test\n    func `converts snapshot to usage snapshot`() throws {\n        let now = Date()\n        let resetDate = try #require(Calendar.current.date(byAdding: .day, value: 7, to: now))\n\n        let snapshot = KiroUsageSnapshot(\n            planName: \"KIRO PRO\",\n            creditsUsed: 25.0,\n            creditsTotal: 100.0,\n            creditsPercent: 25.0,\n            bonusCreditsUsed: 5.0,\n            bonusCreditsTotal: 20.0,\n            bonusExpiryDays: 14,\n            resetsAt: resetDate,\n            updatedAt: now)\n\n        let usage = snapshot.toUsageSnapshot()\n\n        #expect(usage.primary?.usedPercent == 25.0)\n        #expect(usage.primary?.resetsAt == resetDate)\n        #expect(usage.secondary?.usedPercent == 25.0) // 5/20 * 100\n        #expect(usage.loginMethod(for: .kiro) == \"KIRO PRO\")\n        #expect(usage.accountOrganization(for: .kiro) == \"KIRO PRO\")\n    }\n\n    @Test\n    func `converts snapshot without bonus credits`() {\n        let snapshot = KiroUsageSnapshot(\n            planName: \"KIRO FREE\",\n            creditsUsed: 10.0,\n            creditsTotal: 50.0,\n            creditsPercent: 20.0,\n            bonusCreditsUsed: nil,\n            bonusCreditsTotal: nil,\n            bonusExpiryDays: nil,\n            resetsAt: nil,\n            updatedAt: Date())\n\n        let usage = snapshot.toUsageSnapshot()\n\n        #expect(usage.primary?.usedPercent == 20.0)\n        #expect(usage.secondary == nil)\n    }\n\n    // MARK: - Error Cases\n\n    @Test\n    func `empty output throws parse error`() {\n        let probe = KiroStatusProbe()\n\n        #expect(throws: KiroStatusProbeError.self) {\n            try probe.parse(output: \"\")\n        }\n    }\n\n    @Test\n    func `warning output throws parse error`() {\n        let output = \"\"\"\n        \\u{001B}[38;5;11m⚠️  Warning: Could not retrieve usage information from backend\n        \\u{001B}[38;5;8mError: dispatch failure (io error): an i/o error occurred\n        \"\"\"\n\n        let probe = KiroStatusProbe()\n\n        #expect(throws: KiroStatusProbeError.self) {\n            try probe.parse(output: output)\n        }\n    }\n\n    @Test\n    func `unrecognized format throws parse error`() {\n        // Simulates a CLI format change where none of the expected patterns match\n        let output = \"\"\"\n        Welcome to Kiro!\n        Your account is active.\n        Usage: unknown format\n        \"\"\"\n\n        let probe = KiroStatusProbe()\n\n        #expect {\n            try probe.parse(output: output)\n        } throws: { error in\n            guard case let KiroStatusProbeError.parseError(msg) = error else { return false }\n            return msg.contains(\"No recognizable usage patterns\")\n        }\n    }\n\n    @Test\n    func `login prompt throws not logged in`() {\n        let output = \"\"\"\n        Failed to initialize auth portal.\n        Please try again with: kiro-cli login --use-device-flow\n        error: OAuth error: All callback ports are in use.\n        \"\"\"\n\n        let probe = KiroStatusProbe()\n\n        #expect {\n            try probe.parse(output: output)\n        } throws: { error in\n            guard case KiroStatusProbeError.notLoggedIn = error else { return false }\n            return true\n        }\n    }\n\n    // MARK: - WhoAmI Validation\n\n    @Test\n    func `whoami not logged in throws`() {\n        let probe = KiroStatusProbe()\n\n        #expect {\n            try probe.validateWhoAmIOutput(stdout: \"Not logged in\", stderr: \"\", terminationStatus: 1)\n        } throws: { error in\n            guard case KiroStatusProbeError.notLoggedIn = error else { return false }\n            return true\n        }\n    }\n\n    @Test\n    func `whoami login required throws`() {\n        let probe = KiroStatusProbe()\n\n        #expect {\n            try probe.validateWhoAmIOutput(stdout: \"login required\", stderr: \"\", terminationStatus: 1)\n        } throws: { error in\n            guard case KiroStatusProbeError.notLoggedIn = error else { return false }\n            return true\n        }\n    }\n\n    @Test\n    func `whoami empty output with zero status throws`() {\n        let probe = KiroStatusProbe()\n\n        #expect {\n            try probe.validateWhoAmIOutput(stdout: \"\", stderr: \"\", terminationStatus: 0)\n        } throws: { error in\n            guard case KiroStatusProbeError.cliFailed = error else { return false }\n            return true\n        }\n    }\n\n    @Test\n    func `whoami non zero status with message throws`() {\n        let probe = KiroStatusProbe()\n\n        #expect {\n            try probe.validateWhoAmIOutput(stdout: \"\", stderr: \"Connection error\", terminationStatus: 1)\n        } throws: { error in\n            guard case KiroStatusProbeError.cliFailed = error else { return false }\n            return true\n        }\n    }\n\n    @Test\n    func `whoami success does not throw`() throws {\n        let probe = KiroStatusProbe()\n\n        try probe.validateWhoAmIOutput(\n            stdout: \"user@example.com\",\n            stderr: \"\",\n            terminationStatus: 0)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/LiveAccountTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n@testable import CodexBar\n\n@Suite(.serialized)\nstruct LiveAccountTests {\n    @Test(.disabled(\"Set LIVE_TEST=1 to run live Codex account checks.\"))\n    func `codex account email is present`() async throws {\n        guard ProcessInfo.processInfo.environment[\"LIVE_TEST\"] == \"1\" else { return }\n\n        let fetcher = UsageFetcher()\n        let usage = try await fetcher.loadLatestUsage()\n        guard let email = usage.accountEmail(for: .codex) else {\n            Issue.record(\"Account email missing from RPC usage snapshot\")\n            return\n        }\n\n        let pattern = #\"^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$\"#\n        let regex = try Regex(pattern)\n        #expect(email.contains(regex), \"Email did not match pattern: \\(email)\")\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/LoadingPatternTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBar\n\nstruct LoadingPatternTests {\n    @Test\n    func `values stay within bounds`() {\n        for pattern in LoadingPattern.allCases {\n            for phase in stride(from: 0.0, through: Double.pi * 2, by: Double.pi / 6) {\n                let v = pattern.value(phase: phase)\n                #expect(v >= 0 && v <= 100, \"pattern \\(pattern) out of bounds at phase \\(phase)\")\n            }\n        }\n    }\n\n    @Test\n    func `knight rider ping pongs`() {\n        let pattern = LoadingPattern.knightRider\n        let mid = pattern.value(phase: 0) // sin 0 = 0 => 50\n        let min = pattern.value(phase: -Double.pi / 2) // sin -pi/2 = -1 => 0\n        let max = pattern.value(phase: Double.pi / 2) // sin pi/2 = 1 => 100\n        #expect(min <= mid && mid <= max)\n        #expect(min == 0)\n        #expect(max == 100)\n    }\n\n    @Test\n    func `secondary offset differs`() {\n        let pattern = LoadingPattern.cylon\n        let primary = pattern.value(phase: 0)\n        let secondary = pattern.value(phase: pattern.secondaryOffset)\n        #expect(primary != secondary, \"secondary should be offset from primary\")\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/MenuCardKiloPassTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n@testable import CodexBar\n\nstruct MenuCardKiloPassTests {\n    @Test\n    func `kilo model shows pass before credits and keeps reset with detail`() throws {\n        let now = Date()\n        let metadata = try #require(ProviderDefaults.metadata[.kilo])\n        let snapshot = UsageSnapshot(\n            primary: RateWindow(\n                usedPercent: 0,\n                windowMinutes: nil,\n                resetsAt: nil,\n                resetDescription: \"0/19 credits\"),\n            secondary: RateWindow(\n                usedPercent: 0,\n                windowMinutes: nil,\n                resetsAt: now.addingTimeInterval(27 * 24 * 60 * 60),\n                resetDescription: \"$0.00 / $19.00 (+ $9.50 bonus)\"),\n            tertiary: nil,\n            updatedAt: now,\n            identity: ProviderIdentitySnapshot(\n                providerID: .kilo,\n                accountEmail: nil,\n                accountOrganization: nil,\n                loginMethod: \"Starter · Auto top-up: off\"))\n\n        let model = UsageMenuCardView.Model.make(.init(\n            provider: .kilo,\n            metadata: metadata,\n            snapshot: snapshot,\n            credits: nil,\n            creditsError: nil,\n            dashboard: nil,\n            dashboardError: nil,\n            tokenSnapshot: nil,\n            tokenError: nil,\n            account: AccountInfo(email: nil, plan: nil),\n            isRefreshing: false,\n            lastError: nil,\n            usageBarsShowUsed: false,\n            resetTimeDisplayStyle: .countdown,\n            tokenCostUsageEnabled: false,\n            showOptionalCreditsAndExtraUsage: true,\n            hidePersonalInfo: false,\n            now: now))\n\n        #expect(model.metrics.prefix(2).map(\\.id) == [\"secondary\", \"primary\"])\n        let passMetric = try #require(model.metrics.first)\n        #expect(passMetric.title == \"Kilo Pass\")\n        #expect(passMetric.resetText != nil)\n        #expect(passMetric.detailText == \"$0.00 / $19.00 (+ $9.50 bonus)\")\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/MenuCardModelTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport SwiftUI\nimport Testing\n@testable import CodexBar\n\nstruct MenuCardModelTests {\n    @Test\n    func `builds metrics using remaining percent`() throws {\n        let now = Date()\n        let identity = ProviderIdentitySnapshot(\n            providerID: .codex,\n            accountEmail: \"codex@example.com\",\n            accountOrganization: nil,\n            loginMethod: \"Plus Plan\")\n        let snapshot = UsageSnapshot(\n            primary: RateWindow(\n                usedPercent: 22,\n                windowMinutes: 300,\n                resetsAt: now.addingTimeInterval(3000),\n                resetDescription: nil),\n            secondary: RateWindow(\n                usedPercent: 40,\n                windowMinutes: 10080,\n                resetsAt: now.addingTimeInterval(6000),\n                resetDescription: nil),\n            updatedAt: now,\n            identity: identity)\n        let metadata = try #require(ProviderDefaults.metadata[.codex])\n        let updatedSnap = try UsageSnapshot(\n            primary: snapshot.primary,\n            secondary: RateWindow(\n                usedPercent: #require(snapshot.secondary?.usedPercent),\n                windowMinutes: #require(snapshot.secondary?.windowMinutes),\n                resetsAt: now.addingTimeInterval(3600),\n                resetDescription: nil),\n            tertiary: snapshot.tertiary,\n            updatedAt: now,\n            identity: identity)\n\n        let model = UsageMenuCardView.Model.make(.init(\n            provider: .codex,\n            metadata: metadata,\n            snapshot: updatedSnap,\n            credits: CreditsSnapshot(remaining: 12, events: [], updatedAt: now),\n            creditsError: nil,\n            dashboard: nil,\n            dashboardError: nil,\n            tokenSnapshot: nil,\n            tokenError: nil,\n            account: AccountInfo(email: \"codex@example.com\", plan: \"Plus Plan\"),\n            isRefreshing: false,\n            lastError: nil,\n            usageBarsShowUsed: false,\n            resetTimeDisplayStyle: .countdown,\n            tokenCostUsageEnabled: false,\n            showOptionalCreditsAndExtraUsage: true,\n            hidePersonalInfo: false,\n            now: now))\n\n        #expect(model.providerName == \"Codex\")\n        #expect(model.metrics.count == 2)\n        #expect(model.metrics.first?.percent == 78)\n        #expect(model.planText == \"Plus\")\n        #expect(model.subtitleText.hasPrefix(\"Updated\"))\n        #expect(model.progressColor != Color.clear)\n        #expect(model.metrics[1].resetText?.isEmpty == false)\n    }\n\n    @Test\n    func `builds metrics using used percent when enabled`() throws {\n        let now = Date()\n        let identity = ProviderIdentitySnapshot(\n            providerID: .codex,\n            accountEmail: \"codex@example.com\",\n            accountOrganization: nil,\n            loginMethod: \"Plus Plan\")\n        let snapshot = UsageSnapshot(\n            primary: RateWindow(\n                usedPercent: 22,\n                windowMinutes: 300,\n                resetsAt: now.addingTimeInterval(3000),\n                resetDescription: nil),\n            secondary: RateWindow(\n                usedPercent: 40,\n                windowMinutes: 10080,\n                resetsAt: now.addingTimeInterval(6000),\n                resetDescription: nil),\n            updatedAt: now,\n            identity: identity)\n        let metadata = try #require(ProviderDefaults.metadata[.codex])\n\n        let dashboard = OpenAIDashboardSnapshot(\n            signedInEmail: \"codex@example.com\",\n            codeReviewRemainingPercent: 73,\n            creditEvents: [],\n            dailyBreakdown: [],\n            usageBreakdown: [],\n            creditsPurchaseURL: nil,\n            updatedAt: now)\n\n        let model = UsageMenuCardView.Model.make(.init(\n            provider: .codex,\n            metadata: metadata,\n            snapshot: snapshot,\n            credits: nil,\n            creditsError: nil,\n            dashboard: dashboard,\n            dashboardError: nil,\n            tokenSnapshot: nil,\n            tokenError: nil,\n            account: AccountInfo(email: \"codex@example.com\", plan: \"Plus Plan\"),\n            isRefreshing: false,\n            lastError: nil,\n            usageBarsShowUsed: true,\n            resetTimeDisplayStyle: .countdown,\n            tokenCostUsageEnabled: false,\n            showOptionalCreditsAndExtraUsage: true,\n            hidePersonalInfo: false,\n            now: now))\n\n        #expect(model.metrics.first?.title == \"Session\")\n        #expect(model.metrics.first?.percent == 22)\n        #expect(model.metrics.first?.percentLabel.contains(\"used\") == true)\n        #expect(model.metrics.contains { $0.title == \"Code review\" && $0.percent == 27 })\n    }\n\n    @Test\n    func `shows code review metric when dashboard present`() throws {\n        let now = Date()\n        let identity = ProviderIdentitySnapshot(\n            providerID: .codex,\n            accountEmail: \"codex@example.com\",\n            accountOrganization: nil,\n            loginMethod: nil)\n        let snapshot = UsageSnapshot(\n            primary: RateWindow(usedPercent: 0, windowMinutes: 300, resetsAt: nil, resetDescription: nil),\n            secondary: nil,\n            tertiary: nil,\n            updatedAt: now,\n            identity: identity)\n        let metadata = try #require(ProviderDefaults.metadata[.codex])\n\n        let dashboard = OpenAIDashboardSnapshot(\n            signedInEmail: \"codex@example.com\",\n            codeReviewRemainingPercent: 73,\n            creditEvents: [],\n            dailyBreakdown: [],\n            usageBreakdown: [],\n            creditsPurchaseURL: nil,\n            updatedAt: now)\n        let model = UsageMenuCardView.Model.make(.init(\n            provider: .codex,\n            metadata: metadata,\n            snapshot: snapshot,\n            credits: nil,\n            creditsError: nil,\n            dashboard: dashboard,\n            dashboardError: nil,\n            tokenSnapshot: nil,\n            tokenError: nil,\n            account: AccountInfo(email: \"codex@example.com\", plan: nil),\n            isRefreshing: false,\n            lastError: nil,\n            usageBarsShowUsed: false,\n            resetTimeDisplayStyle: .countdown,\n            tokenCostUsageEnabled: false,\n            showOptionalCreditsAndExtraUsage: true,\n            hidePersonalInfo: false,\n            now: now))\n\n        #expect(model.metrics.contains { $0.title == \"Code review\" && $0.percent == 73 })\n    }\n\n    @Test\n    func `claude model hides weekly when unavailable`() throws {\n        let now = Date()\n        let identity = ProviderIdentitySnapshot(\n            providerID: .claude,\n            accountEmail: nil,\n            accountOrganization: nil,\n            loginMethod: \"Max\")\n        let snapshot = UsageSnapshot(\n            primary: RateWindow(\n                usedPercent: 2,\n                windowMinutes: nil,\n                resetsAt: now.addingTimeInterval(3600),\n                resetDescription: nil),\n            secondary: nil,\n            tertiary: nil,\n            updatedAt: now,\n            identity: identity)\n        let metadata = try #require(ProviderDefaults.metadata[.claude])\n        let model = UsageMenuCardView.Model.make(.init(\n            provider: .claude,\n            metadata: metadata,\n            snapshot: snapshot,\n            credits: nil,\n            creditsError: nil,\n            dashboard: nil,\n            dashboardError: nil,\n            tokenSnapshot: nil,\n            tokenError: nil,\n            account: AccountInfo(email: \"codex@example.com\", plan: \"plus\"),\n            isRefreshing: false,\n            lastError: nil,\n            usageBarsShowUsed: false,\n            resetTimeDisplayStyle: .countdown,\n            tokenCostUsageEnabled: false,\n            showOptionalCreditsAndExtraUsage: true,\n            hidePersonalInfo: false,\n            now: now))\n\n        #expect(model.metrics.count == 1)\n        #expect(model.metrics.first?.title == \"Session\")\n        #expect(model.planText == \"Max\")\n    }\n\n    @Test\n    func `shows error subtitle when present`() throws {\n        let metadata = try #require(ProviderDefaults.metadata[.codex])\n        let model = UsageMenuCardView.Model.make(.init(\n            provider: .codex,\n            metadata: metadata,\n            snapshot: nil,\n            credits: nil,\n            creditsError: nil,\n            dashboard: nil,\n            dashboardError: nil,\n            tokenSnapshot: nil,\n            tokenError: nil,\n            account: AccountInfo(email: nil, plan: nil),\n            isRefreshing: false,\n            lastError: \"Probe failed for Codex\",\n            usageBarsShowUsed: false,\n            resetTimeDisplayStyle: .countdown,\n            tokenCostUsageEnabled: false,\n            showOptionalCreditsAndExtraUsage: true,\n            hidePersonalInfo: false,\n            now: Date()))\n\n        #expect(model.subtitleStyle == .error)\n        #expect(model.subtitleText.contains(\"Probe failed\"))\n        #expect(model.placeholder == nil)\n    }\n\n    @Test\n    func `cost section includes last30 days tokens`() throws {\n        let now = Date()\n        let metadata = try #require(ProviderDefaults.metadata[.codex])\n        let snapshot = UsageSnapshot(\n            primary: RateWindow(usedPercent: 0, windowMinutes: 300, resetsAt: nil, resetDescription: nil),\n            secondary: nil,\n            tertiary: nil,\n            updatedAt: now)\n        let tokenSnapshot = CostUsageTokenSnapshot(\n            sessionTokens: 123,\n            sessionCostUSD: 1.23,\n            last30DaysTokens: 456,\n            last30DaysCostUSD: 78.9,\n            daily: [],\n            updatedAt: now)\n        let model = UsageMenuCardView.Model.make(.init(\n            provider: .codex,\n            metadata: metadata,\n            snapshot: snapshot,\n            credits: nil,\n            creditsError: nil,\n            dashboard: nil,\n            dashboardError: nil,\n            tokenSnapshot: tokenSnapshot,\n            tokenError: nil,\n            account: AccountInfo(email: nil, plan: nil),\n            isRefreshing: false,\n            lastError: nil,\n            usageBarsShowUsed: false,\n            resetTimeDisplayStyle: .countdown,\n            tokenCostUsageEnabled: true,\n            showOptionalCreditsAndExtraUsage: true,\n            hidePersonalInfo: false,\n            now: now))\n\n        #expect(model.tokenUsage?.monthLine.contains(\"456\") == true)\n        #expect(model.tokenUsage?.monthLine.contains(\"tokens\") == true)\n    }\n\n    @Test\n    func `claude model does not leak codex plan`() throws {\n        let metadata = try #require(ProviderDefaults.metadata[.claude])\n        let model = UsageMenuCardView.Model.make(.init(\n            provider: .claude,\n            metadata: metadata,\n            snapshot: nil,\n            credits: nil,\n            creditsError: nil,\n            dashboard: nil,\n            dashboardError: nil,\n            tokenSnapshot: nil,\n            tokenError: nil,\n            account: AccountInfo(email: \"codex@example.com\", plan: \"plus\"),\n            isRefreshing: false,\n            lastError: nil,\n            usageBarsShowUsed: false,\n            resetTimeDisplayStyle: .countdown,\n            tokenCostUsageEnabled: false,\n            showOptionalCreditsAndExtraUsage: true,\n            hidePersonalInfo: false,\n            now: Date()))\n\n        #expect(model.planText == nil)\n        #expect(model.email.isEmpty)\n    }\n\n    @Test\n    func `hides codex credits when disabled`() throws {\n        let now = Date()\n        let identity = ProviderIdentitySnapshot(\n            providerID: .codex,\n            accountEmail: \"codex@example.com\",\n            accountOrganization: nil,\n            loginMethod: nil)\n        let snapshot = UsageSnapshot(\n            primary: RateWindow(usedPercent: 0, windowMinutes: 300, resetsAt: nil, resetDescription: nil),\n            secondary: nil,\n            tertiary: nil,\n            updatedAt: now,\n            identity: identity)\n        let metadata = try #require(ProviderDefaults.metadata[.codex])\n\n        let model = UsageMenuCardView.Model.make(.init(\n            provider: .codex,\n            metadata: metadata,\n            snapshot: snapshot,\n            credits: CreditsSnapshot(remaining: 12, events: [], updatedAt: now),\n            creditsError: nil,\n            dashboard: nil,\n            dashboardError: nil,\n            tokenSnapshot: nil,\n            tokenError: nil,\n            account: AccountInfo(email: \"codex@example.com\", plan: nil),\n            isRefreshing: false,\n            lastError: nil,\n            usageBarsShowUsed: false,\n            resetTimeDisplayStyle: .countdown,\n            tokenCostUsageEnabled: false,\n            showOptionalCreditsAndExtraUsage: false,\n            hidePersonalInfo: false,\n            now: now))\n\n        #expect(model.creditsText == nil)\n    }\n\n    @Test\n    func `hides claude extra usage when disabled`() throws {\n        let now = Date()\n        let identity = ProviderIdentitySnapshot(\n            providerID: .claude,\n            accountEmail: \"claude@example.com\",\n            accountOrganization: nil,\n            loginMethod: nil)\n        let snapshot = UsageSnapshot(\n            primary: RateWindow(usedPercent: 0, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            secondary: nil,\n            tertiary: nil,\n            providerCost: ProviderCostSnapshot(used: 12, limit: 200, currencyCode: \"USD\", updatedAt: now),\n            updatedAt: now,\n            identity: identity)\n        let metadata = try #require(ProviderDefaults.metadata[.claude])\n\n        let model = UsageMenuCardView.Model.make(.init(\n            provider: .claude,\n            metadata: metadata,\n            snapshot: snapshot,\n            credits: nil,\n            creditsError: nil,\n            dashboard: nil,\n            dashboardError: nil,\n            tokenSnapshot: nil,\n            tokenError: nil,\n            account: AccountInfo(email: nil, plan: nil),\n            isRefreshing: false,\n            lastError: nil,\n            usageBarsShowUsed: false,\n            resetTimeDisplayStyle: .countdown,\n            tokenCostUsageEnabled: false,\n            showOptionalCreditsAndExtraUsage: false,\n            hidePersonalInfo: false,\n            now: now))\n\n        #expect(model.providerCost == nil)\n    }\n\n    @Test\n    @MainActor\n    func `open router model uses API key quota bar and quota detail`() throws {\n        let now = Date()\n        let metadata = try #require(ProviderDefaults.metadata[.openrouter])\n        let snapshot = OpenRouterUsageSnapshot(\n            totalCredits: 50,\n            totalUsage: 45.3895596325,\n            balance: 4.6104403675,\n            usedPercent: 90.779119265,\n            keyLimit: 20,\n            keyUsage: 0.5,\n            rateLimit: nil,\n            updatedAt: now).toUsageSnapshot()\n\n        let model = UsageMenuCardView.Model.make(.init(\n            provider: .openrouter,\n            metadata: metadata,\n            snapshot: snapshot,\n            credits: nil,\n            creditsError: nil,\n            dashboard: nil,\n            dashboardError: nil,\n            tokenSnapshot: nil,\n            tokenError: nil,\n            account: AccountInfo(email: nil, plan: nil),\n            isRefreshing: false,\n            lastError: nil,\n            usageBarsShowUsed: false,\n            resetTimeDisplayStyle: .countdown,\n            tokenCostUsageEnabled: false,\n            showOptionalCreditsAndExtraUsage: true,\n            hidePersonalInfo: false,\n            now: now))\n\n        #expect(model.creditsText == nil)\n        #expect(model.metrics.count == 1)\n        #expect(model.usageNotes.isEmpty)\n        let metric = try #require(model.metrics.first)\n        let popupTitle = UsageMenuCardView.popupMetricTitle(\n            provider: .openrouter,\n            metric: metric)\n        #expect(popupTitle == \"API key limit\")\n        #expect(metric.resetText == \"$19.50/$20.00 left\")\n        #expect(metric.detailRightText == nil)\n    }\n\n    @Test\n    func `open router model without key limit shows text only summary`() throws {\n        let now = Date()\n        let metadata = try #require(ProviderDefaults.metadata[.openrouter])\n        let snapshot = OpenRouterUsageSnapshot(\n            totalCredits: 50,\n            totalUsage: 45.3895596325,\n            balance: 4.6104403675,\n            usedPercent: 90.779119265,\n            keyDataFetched: true,\n            keyLimit: nil,\n            keyUsage: nil,\n            rateLimit: nil,\n            updatedAt: now).toUsageSnapshot()\n\n        let model = UsageMenuCardView.Model.make(.init(\n            provider: .openrouter,\n            metadata: metadata,\n            snapshot: snapshot,\n            credits: nil,\n            creditsError: nil,\n            dashboard: nil,\n            dashboardError: nil,\n            tokenSnapshot: nil,\n            tokenError: nil,\n            account: AccountInfo(email: nil, plan: nil),\n            isRefreshing: false,\n            lastError: nil,\n            usageBarsShowUsed: false,\n            resetTimeDisplayStyle: .countdown,\n            tokenCostUsageEnabled: false,\n            showOptionalCreditsAndExtraUsage: true,\n            hidePersonalInfo: false,\n            now: now))\n\n        #expect(model.metrics.isEmpty)\n        #expect(model.creditsText == nil)\n        #expect(model.placeholder == nil)\n        #expect(model.usageNotes == [\"No limit set for the API key\"])\n    }\n\n    @Test\n    func `open router model when key fetch unavailable shows unavailable note`() throws {\n        let now = Date()\n        let metadata = try #require(ProviderDefaults.metadata[.openrouter])\n        let snapshot = OpenRouterUsageSnapshot(\n            totalCredits: 50,\n            totalUsage: 45.3895596325,\n            balance: 4.6104403675,\n            usedPercent: 90.779119265,\n            keyDataFetched: false,\n            keyLimit: nil,\n            keyUsage: nil,\n            rateLimit: nil,\n            updatedAt: now).toUsageSnapshot()\n\n        let model = UsageMenuCardView.Model.make(.init(\n            provider: .openrouter,\n            metadata: metadata,\n            snapshot: snapshot,\n            credits: nil,\n            creditsError: nil,\n            dashboard: nil,\n            dashboardError: nil,\n            tokenSnapshot: nil,\n            tokenError: nil,\n            account: AccountInfo(email: nil, plan: nil),\n            isRefreshing: false,\n            lastError: nil,\n            usageBarsShowUsed: false,\n            resetTimeDisplayStyle: .countdown,\n            tokenCostUsageEnabled: false,\n            showOptionalCreditsAndExtraUsage: true,\n            hidePersonalInfo: false,\n            now: now))\n\n        #expect(model.metrics.isEmpty)\n        #expect(model.usageNotes == [\"API key limit unavailable right now\"])\n    }\n\n    @Test\n    func `hides email when personal info hidden`() throws {\n        let now = Date()\n        let identity = ProviderIdentitySnapshot(\n            providerID: .codex,\n            accountEmail: \"codex@example.com\",\n            accountOrganization: nil,\n            loginMethod: nil)\n        let snapshot = UsageSnapshot(\n            primary: RateWindow(usedPercent: 0, windowMinutes: 300, resetsAt: nil, resetDescription: nil),\n            secondary: nil,\n            tertiary: nil,\n            updatedAt: now,\n            identity: identity)\n        let metadata = try #require(ProviderDefaults.metadata[.codex])\n\n        let model = UsageMenuCardView.Model.make(.init(\n            provider: .codex,\n            metadata: metadata,\n            snapshot: snapshot,\n            credits: nil,\n            creditsError: nil,\n            dashboard: nil,\n            dashboardError: \"OpenAI dashboard signed in as codex@example.com.\",\n            tokenSnapshot: nil,\n            tokenError: nil,\n            account: AccountInfo(email: \"codex@example.com\", plan: nil),\n            isRefreshing: false,\n            lastError: \"OpenAI dashboard signed in as codex@example.com.\",\n            usageBarsShowUsed: false,\n            resetTimeDisplayStyle: .countdown,\n            tokenCostUsageEnabled: false,\n            showOptionalCreditsAndExtraUsage: true,\n            hidePersonalInfo: true,\n            now: now))\n\n        #expect(model.email == \"Hidden\")\n        #expect(model.subtitleText.contains(\"codex@example.com\") == false)\n        #expect(model.creditsHintCopyText?.isEmpty == true)\n        #expect(model.creditsHintText?.contains(\"codex@example.com\") == false)\n    }\n\n    @Test\n    func `kilo model splits pass and activity and shows fallback note`() throws {\n        let now = Date()\n        let metadata = try #require(ProviderDefaults.metadata[.kilo])\n        let snapshot = UsageSnapshot(\n            primary: RateWindow(\n                usedPercent: 40,\n                windowMinutes: nil,\n                resetsAt: nil,\n                resetDescription: \"40/100 credits\"),\n            secondary: nil,\n            tertiary: nil,\n            updatedAt: now,\n            identity: ProviderIdentitySnapshot(\n                providerID: .kilo,\n                accountEmail: nil,\n                accountOrganization: nil,\n                loginMethod: \"Kilo Pass Pro · Auto top-up: visa\"))\n\n        let model = UsageMenuCardView.Model.make(.init(\n            provider: .kilo,\n            metadata: metadata,\n            snapshot: snapshot,\n            credits: nil,\n            creditsError: nil,\n            dashboard: nil,\n            dashboardError: nil,\n            tokenSnapshot: nil,\n            tokenError: nil,\n            account: AccountInfo(email: nil, plan: nil),\n            isRefreshing: false,\n            lastError: nil,\n            usageBarsShowUsed: false,\n            resetTimeDisplayStyle: .countdown,\n            tokenCostUsageEnabled: false,\n            showOptionalCreditsAndExtraUsage: true,\n            sourceLabel: \"cli\",\n            kiloAutoMode: true,\n            hidePersonalInfo: false,\n            now: now))\n\n        #expect(model.planText == \"Kilo Pass Pro\")\n        #expect(model.usageNotes.contains(\"Auto top-up: visa\"))\n        #expect(model.usageNotes.contains(\"Using CLI fallback\"))\n    }\n\n    @Test\n    func `kilo model treats auto top up only login as activity`() throws {\n        let now = Date()\n        let metadata = try #require(ProviderDefaults.metadata[.kilo])\n        let snapshot = UsageSnapshot(\n            primary: nil,\n            secondary: nil,\n            tertiary: nil,\n            updatedAt: now,\n            identity: ProviderIdentitySnapshot(\n                providerID: .kilo,\n                accountEmail: nil,\n                accountOrganization: nil,\n                loginMethod: \"Auto top-up: off\"))\n\n        let model = UsageMenuCardView.Model.make(.init(\n            provider: .kilo,\n            metadata: metadata,\n            snapshot: snapshot,\n            credits: nil,\n            creditsError: nil,\n            dashboard: nil,\n            dashboardError: nil,\n            tokenSnapshot: nil,\n            tokenError: nil,\n            account: AccountInfo(email: nil, plan: nil),\n            isRefreshing: false,\n            lastError: nil,\n            usageBarsShowUsed: false,\n            resetTimeDisplayStyle: .countdown,\n            tokenCostUsageEnabled: false,\n            showOptionalCreditsAndExtraUsage: true,\n            hidePersonalInfo: false,\n            now: now))\n\n        #expect(model.planText == nil)\n        #expect(model.usageNotes.contains(\"Auto top-up: off\"))\n    }\n\n    @Test\n    func `kilo model does not show fallback note when not auto to CLI`() throws {\n        let now = Date()\n        let metadata = try #require(ProviderDefaults.metadata[.kilo])\n        let snapshot = UsageSnapshot(\n            primary: RateWindow(\n                usedPercent: 40,\n                windowMinutes: nil,\n                resetsAt: nil,\n                resetDescription: \"40/100 credits\"),\n            secondary: nil,\n            tertiary: nil,\n            updatedAt: now,\n            identity: ProviderIdentitySnapshot(\n                providerID: .kilo,\n                accountEmail: nil,\n                accountOrganization: nil,\n                loginMethod: \"Kilo Pass Pro · Auto top-up: visa\"))\n\n        let apiModel = UsageMenuCardView.Model.make(.init(\n            provider: .kilo,\n            metadata: metadata,\n            snapshot: snapshot,\n            credits: nil,\n            creditsError: nil,\n            dashboard: nil,\n            dashboardError: nil,\n            tokenSnapshot: nil,\n            tokenError: nil,\n            account: AccountInfo(email: nil, plan: nil),\n            isRefreshing: false,\n            lastError: nil,\n            usageBarsShowUsed: false,\n            resetTimeDisplayStyle: .countdown,\n            tokenCostUsageEnabled: false,\n            showOptionalCreditsAndExtraUsage: true,\n            sourceLabel: \"api\",\n            kiloAutoMode: true,\n            hidePersonalInfo: false,\n            now: now))\n\n        let nonAutoModel = UsageMenuCardView.Model.make(.init(\n            provider: .kilo,\n            metadata: metadata,\n            snapshot: snapshot,\n            credits: nil,\n            creditsError: nil,\n            dashboard: nil,\n            dashboardError: nil,\n            tokenSnapshot: nil,\n            tokenError: nil,\n            account: AccountInfo(email: nil, plan: nil),\n            isRefreshing: false,\n            lastError: nil,\n            usageBarsShowUsed: false,\n            resetTimeDisplayStyle: .countdown,\n            tokenCostUsageEnabled: false,\n            showOptionalCreditsAndExtraUsage: true,\n            sourceLabel: \"cli\",\n            kiloAutoMode: false,\n            hidePersonalInfo: false,\n            now: now))\n\n        #expect(!apiModel.usageNotes.contains(\"Using CLI fallback\"))\n        #expect(!nonAutoModel.usageNotes.contains(\"Using CLI fallback\"))\n    }\n\n    @Test\n    func `kilo model shows primary detail when reset date missing`() throws {\n        let now = Date()\n        let metadata = try #require(ProviderDefaults.metadata[.kilo])\n        let snapshot = UsageSnapshot(\n            primary: RateWindow(\n                usedPercent: 10,\n                windowMinutes: nil,\n                resetsAt: nil,\n                resetDescription: \"10/100 credits\"),\n            secondary: nil,\n            tertiary: nil,\n            updatedAt: now,\n            identity: ProviderIdentitySnapshot(\n                providerID: .kilo,\n                accountEmail: nil,\n                accountOrganization: nil,\n                loginMethod: \"Kilo Pass Pro\"))\n\n        let model = UsageMenuCardView.Model.make(.init(\n            provider: .kilo,\n            metadata: metadata,\n            snapshot: snapshot,\n            credits: nil,\n            creditsError: nil,\n            dashboard: nil,\n            dashboardError: nil,\n            tokenSnapshot: nil,\n            tokenError: nil,\n            account: AccountInfo(email: nil, plan: nil),\n            isRefreshing: false,\n            lastError: nil,\n            usageBarsShowUsed: false,\n            resetTimeDisplayStyle: .countdown,\n            tokenCostUsageEnabled: false,\n            showOptionalCreditsAndExtraUsage: true,\n            hidePersonalInfo: false,\n            now: now))\n\n        let primary = try #require(model.metrics.first)\n        #expect(primary.resetText == nil)\n        #expect(primary.detailText == \"10/100 credits\")\n    }\n\n    @Test\n    func `kilo model keeps zero total edge state visible`() throws {\n        let now = Date()\n        let metadata = try #require(ProviderDefaults.metadata[.kilo])\n        let snapshot = KiloUsageSnapshot(\n            creditsUsed: 0,\n            creditsTotal: 0,\n            creditsRemaining: 0,\n            planName: \"Kilo Pass Pro\",\n            autoTopUpEnabled: true,\n            autoTopUpMethod: \"visa\",\n            updatedAt: now).toUsageSnapshot()\n\n        let model = UsageMenuCardView.Model.make(.init(\n            provider: .kilo,\n            metadata: metadata,\n            snapshot: snapshot,\n            credits: nil,\n            creditsError: nil,\n            dashboard: nil,\n            dashboardError: nil,\n            tokenSnapshot: nil,\n            tokenError: nil,\n            account: AccountInfo(email: nil, plan: nil),\n            isRefreshing: false,\n            lastError: nil,\n            usageBarsShowUsed: false,\n            resetTimeDisplayStyle: .countdown,\n            tokenCostUsageEnabled: false,\n            showOptionalCreditsAndExtraUsage: true,\n            hidePersonalInfo: false,\n            now: now))\n\n        let primary = try #require(model.metrics.first)\n        #expect(primary.percent == 0)\n        #expect(primary.detailText == \"0/0 credits\")\n        #expect(model.placeholder == nil)\n    }\n\n    @Test\n    func `warp model shows primary detail when reset date missing`() throws {\n        let now = Date()\n        let identity = ProviderIdentitySnapshot(\n            providerID: .warp,\n            accountEmail: nil,\n            accountOrganization: nil,\n            loginMethod: nil)\n        let snapshot = UsageSnapshot(\n            primary: RateWindow(\n                usedPercent: 10,\n                windowMinutes: nil,\n                resetsAt: nil,\n                resetDescription: \"10/100 credits\"),\n            secondary: nil,\n            tertiary: nil,\n            updatedAt: now,\n            identity: identity)\n        let metadata = try #require(ProviderDefaults.metadata[.warp])\n\n        let model = UsageMenuCardView.Model.make(.init(\n            provider: .warp,\n            metadata: metadata,\n            snapshot: snapshot,\n            credits: nil,\n            creditsError: nil,\n            dashboard: nil,\n            dashboardError: nil,\n            tokenSnapshot: nil,\n            tokenError: nil,\n            account: AccountInfo(email: nil, plan: nil),\n            isRefreshing: false,\n            lastError: nil,\n            usageBarsShowUsed: true,\n            resetTimeDisplayStyle: .countdown,\n            tokenCostUsageEnabled: false,\n            showOptionalCreditsAndExtraUsage: true,\n            hidePersonalInfo: false,\n            now: now))\n\n        let primary = try #require(model.metrics.first)\n        #expect(primary.resetText == nil)\n        #expect(primary.detailText == \"10/100 credits\")\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/MenuDescriptorKiloTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n@testable import CodexBar\n\n@MainActor\nstruct MenuDescriptorKiloTests {\n    @Test\n    func `kilo credits detail does not render as reset line`() throws {\n        let suite = \"MenuDescriptorKiloTests-kilo-detail\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n\n        let settings = SettingsStore(\n            userDefaults: defaults,\n            configStore: testConfigStore(suiteName: suite),\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n        settings.statusChecksEnabled = false\n        settings.usageBarsShowUsed = false\n\n        let store = UsageStore(\n            fetcher: UsageFetcher(environment: [:]),\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            settings: settings)\n        let snapshot = UsageSnapshot(\n            primary: RateWindow(\n                usedPercent: 10,\n                windowMinutes: nil,\n                resetsAt: nil,\n                resetDescription: \"10/100 credits\"),\n            secondary: nil,\n            tertiary: nil,\n            updatedAt: Date(),\n            identity: ProviderIdentitySnapshot(\n                providerID: .kilo,\n                accountEmail: nil,\n                accountOrganization: nil,\n                loginMethod: \"Kilo Pass Pro\"))\n        store._setSnapshotForTesting(snapshot, provider: .kilo)\n\n        let descriptor = MenuDescriptor.build(\n            provider: .kilo,\n            store: store,\n            settings: settings,\n            account: AccountInfo(email: nil, plan: nil),\n            updateReady: false,\n            includeContextualActions: false)\n\n        let usageEntries = try #require(descriptor.sections.first?.entries)\n        let textLines = usageEntries.compactMap { entry -> String? in\n            guard case let .text(text, _) = entry else { return nil }\n            return text\n        }\n\n        #expect(textLines.contains(\"10/100 credits\"))\n        #expect(!textLines.contains(where: { $0.contains(\"Resets 10/100 credits\") }))\n    }\n\n    @Test\n    func `kilo pass detail keeps reset line when reset date exists`() throws {\n        let suite = \"MenuDescriptorKiloTests-kilo-pass-reset\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n\n        let settings = SettingsStore(\n            userDefaults: defaults,\n            configStore: testConfigStore(suiteName: suite),\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n        settings.statusChecksEnabled = false\n        settings.usageBarsShowUsed = false\n\n        let store = UsageStore(\n            fetcher: UsageFetcher(environment: [:]),\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            settings: settings)\n        let snapshot = UsageSnapshot(\n            primary: RateWindow(\n                usedPercent: 0,\n                windowMinutes: nil,\n                resetsAt: nil,\n                resetDescription: \"0/19 credits\"),\n            secondary: RateWindow(\n                usedPercent: 0,\n                windowMinutes: nil,\n                resetsAt: Date().addingTimeInterval(2 * 24 * 60 * 60),\n                resetDescription: \"$0.00 / $19.00 (+ $9.50 bonus)\"),\n            tertiary: nil,\n            updatedAt: Date(),\n            identity: ProviderIdentitySnapshot(\n                providerID: .kilo,\n                accountEmail: nil,\n                accountOrganization: nil,\n                loginMethod: \"Starter\"))\n        store._setSnapshotForTesting(snapshot, provider: .kilo)\n\n        let descriptor = MenuDescriptor.build(\n            provider: .kilo,\n            store: store,\n            settings: settings,\n            account: AccountInfo(email: nil, plan: nil),\n            updateReady: false,\n            includeContextualActions: false)\n\n        let usageEntries = try #require(descriptor.sections.first?.entries)\n        let textLines = usageEntries.compactMap { entry -> String? in\n            guard case let .text(text, _) = entry else { return nil }\n            return text\n        }\n\n        #expect(textLines.contains(where: { $0.contains(\"Resets\") }))\n        #expect(textLines.contains(\"$0.00 / $19.00 (+ $9.50 bonus)\"))\n    }\n\n    @Test\n    func `kilo auto top up only renders activity without plan label`() throws {\n        let suite = \"MenuDescriptorKiloTests-kilo-activity-only\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n\n        let settings = SettingsStore(\n            userDefaults: defaults,\n            configStore: testConfigStore(suiteName: suite),\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n        settings.statusChecksEnabled = false\n\n        let store = UsageStore(\n            fetcher: UsageFetcher(environment: [:]),\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            settings: settings)\n        let snapshot = UsageSnapshot(\n            primary: RateWindow(\n                usedPercent: 0,\n                windowMinutes: nil,\n                resetsAt: nil,\n                resetDescription: \"0/0 credits\"),\n            secondary: nil,\n            tertiary: nil,\n            updatedAt: Date(),\n            identity: ProviderIdentitySnapshot(\n                providerID: .kilo,\n                accountEmail: nil,\n                accountOrganization: nil,\n                loginMethod: \"Auto top-up: off\"))\n        store._setSnapshotForTesting(snapshot, provider: .kilo)\n\n        let descriptor = MenuDescriptor.build(\n            provider: .kilo,\n            store: store,\n            settings: settings,\n            account: AccountInfo(email: nil, plan: nil),\n            updateReady: false,\n            includeContextualActions: false)\n\n        let textLines = descriptor.sections\n            .flatMap(\\.entries)\n            .compactMap { entry -> String? in\n                guard case let .text(text, _) = entry else { return nil }\n                return text\n            }\n\n        #expect(textLines.contains(\"Activity: Auto top-up: off\"))\n        #expect(!textLines.contains(\"Plan: Auto top-up: off\"))\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/MiniMaxAPITokenFetchTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n\n@Suite(.serialized)\nstruct MiniMaxAPITokenFetchTests {\n    @Test\n    func `retries china host when global rejects token`() async throws {\n        let registered = URLProtocol.registerClass(MiniMaxAPITokenStubURLProtocol.self)\n        defer {\n            if registered {\n                URLProtocol.unregisterClass(MiniMaxAPITokenStubURLProtocol.self)\n            }\n            MiniMaxAPITokenStubURLProtocol.handler = nil\n            MiniMaxAPITokenStubURLProtocol.requests = []\n        }\n\n        MiniMaxAPITokenStubURLProtocol.handler = { request in\n            guard let url = request.url else { throw URLError(.badURL) }\n            let host = url.host ?? \"\"\n            if host == \"api.minimax.io\" {\n                return Self.makeResponse(url: url, body: \"{}\", statusCode: 401)\n            }\n            if host == \"api.minimaxi.com\" {\n                let start = 1_700_000_000_000\n                let end = start + 5 * 60 * 60 * 1000\n                let body = \"\"\"\n                {\n                  \"base_resp\": { \"status_code\": 0 },\n                  \"current_subscribe_title\": \"Max\",\n                  \"model_remains\": [\n                    {\n                      \"current_interval_total_count\": 1000,\n                      \"current_interval_usage_count\": 250,\n                      \"start_time\": \\(start),\n                      \"end_time\": \\(end),\n                      \"remains_time\": 240000\n                    }\n                  ]\n                }\n                \"\"\"\n                return Self.makeResponse(url: url, body: body)\n            }\n            return Self.makeResponse(url: url, body: \"{}\", statusCode: 404)\n        }\n\n        let now = Date(timeIntervalSince1970: 1_700_000_000)\n        let snapshot = try await MiniMaxUsageFetcher.fetchUsage(apiToken: \"sk-cp-test\", region: .global, now: now)\n\n        #expect(snapshot.planName == \"Max\")\n        #expect(MiniMaxAPITokenStubURLProtocol.requests.count == 2)\n        #expect(MiniMaxAPITokenStubURLProtocol.requests.first?.url?.host == \"api.minimax.io\")\n        #expect(MiniMaxAPITokenStubURLProtocol.requests.last?.url?.host == \"api.minimaxi.com\")\n    }\n\n    @Test\n    func `preserves invalid credentials when china retry fails transport`() async throws {\n        let registered = URLProtocol.registerClass(MiniMaxAPITokenStubURLProtocol.self)\n        defer {\n            if registered {\n                URLProtocol.unregisterClass(MiniMaxAPITokenStubURLProtocol.self)\n            }\n            MiniMaxAPITokenStubURLProtocol.handler = nil\n            MiniMaxAPITokenStubURLProtocol.requests = []\n        }\n\n        MiniMaxAPITokenStubURLProtocol.handler = { request in\n            guard let url = request.url else { throw URLError(.badURL) }\n            let host = url.host ?? \"\"\n            if host == \"api.minimax.io\" {\n                return Self.makeResponse(url: url, body: \"{}\", statusCode: 401)\n            }\n            if host == \"api.minimaxi.com\" {\n                throw URLError(.cannotFindHost)\n            }\n            return Self.makeResponse(url: url, body: \"{}\", statusCode: 404)\n        }\n\n        let now = Date(timeIntervalSince1970: 1_700_000_000)\n        await #expect(throws: MiniMaxUsageError.invalidCredentials) {\n            _ = try await MiniMaxUsageFetcher.fetchUsage(apiToken: \"sk-cp-test\", region: .global, now: now)\n        }\n\n        #expect(MiniMaxAPITokenStubURLProtocol.requests.count == 2)\n        #expect(MiniMaxAPITokenStubURLProtocol.requests.first?.url?.host == \"api.minimax.io\")\n        #expect(MiniMaxAPITokenStubURLProtocol.requests.last?.url?.host == \"api.minimaxi.com\")\n    }\n\n    @Test\n    func `does not retry when region is china mainland`() async throws {\n        let registered = URLProtocol.registerClass(MiniMaxAPITokenStubURLProtocol.self)\n        defer {\n            if registered {\n                URLProtocol.unregisterClass(MiniMaxAPITokenStubURLProtocol.self)\n            }\n            MiniMaxAPITokenStubURLProtocol.handler = nil\n            MiniMaxAPITokenStubURLProtocol.requests = []\n        }\n\n        MiniMaxAPITokenStubURLProtocol.handler = { request in\n            guard let url = request.url else { throw URLError(.badURL) }\n            let host = url.host ?? \"\"\n            if host == \"api.minimaxi.com\" {\n                let start = 1_700_000_000_000\n                let end = start + 5 * 60 * 60 * 1000\n                let body = \"\"\"\n                {\n                  \"base_resp\": { \"status_code\": 0 },\n                  \"current_subscribe_title\": \"Max\",\n                  \"model_remains\": [\n                    {\n                      \"current_interval_total_count\": 1000,\n                      \"current_interval_usage_count\": 250,\n                      \"start_time\": \\(start),\n                      \"end_time\": \\(end),\n                      \"remains_time\": 240000\n                    }\n                  ]\n                }\n                \"\"\"\n                return Self.makeResponse(url: url, body: body)\n            }\n            return Self.makeResponse(url: url, body: \"{}\", statusCode: 401)\n        }\n\n        let now = Date(timeIntervalSince1970: 1_700_000_000)\n        _ = try await MiniMaxUsageFetcher.fetchUsage(apiToken: \"sk-cp-test\", region: .chinaMainland, now: now)\n\n        #expect(MiniMaxAPITokenStubURLProtocol.requests.count == 1)\n        #expect(MiniMaxAPITokenStubURLProtocol.requests.first?.url?.host == \"api.minimaxi.com\")\n    }\n\n    private static func makeResponse(\n        url: URL,\n        body: String,\n        statusCode: Int = 200) -> (HTTPURLResponse, Data)\n    {\n        let response = HTTPURLResponse(\n            url: url,\n            statusCode: statusCode,\n            httpVersion: \"HTTP/1.1\",\n            headerFields: [\"Content-Type\": \"application/json\"])!\n        return (response, Data(body.utf8))\n    }\n}\n\nfinal class MiniMaxAPITokenStubURLProtocol: URLProtocol {\n    nonisolated(unsafe) static var handler: ((URLRequest) throws -> (HTTPURLResponse, Data))?\n    nonisolated(unsafe) static var requests: [URLRequest] = []\n\n    override static func canInit(with request: URLRequest) -> Bool {\n        guard let host = request.url?.host else { return false }\n        return host == \"api.minimax.io\" || host == \"api.minimaxi.com\"\n    }\n\n    override static func canonicalRequest(for request: URLRequest) -> URLRequest {\n        request\n    }\n\n    override func startLoading() {\n        Self.requests.append(self.request)\n        guard let handler = Self.handler else {\n            self.client?.urlProtocol(self, didFailWithError: URLError(.badServerResponse))\n            return\n        }\n        do {\n            let (response, data) = try handler(self.request)\n            self.client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)\n            self.client?.urlProtocol(self, didLoad: data)\n            self.client?.urlProtocolDidFinishLoading(self)\n        } catch {\n            self.client?.urlProtocol(self, didFailWithError: error)\n        }\n    }\n\n    override func stopLoading() {}\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/MiniMaxLocalStorageImporterTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\nstruct MiniMaxLocalStorageImporterTests {\n    @Test\n    func `extracts access tokens from JSON preferring long tokens`() {\n        let shortToken = String(repeating: \"b\", count: 24)\n        let longToken = String(repeating: \"a\", count: 72)\n        let payload = \"\"\"\n        {\"access_token\":\"\\(shortToken)\",\"nested\":{\"token\":\"\\(longToken)\"}}\n        \"\"\"\n\n        let tokens = MiniMaxLocalStorageImporter._extractAccessTokensForTesting(payload)\n\n        #expect(tokens.contains(longToken))\n        #expect(tokens.contains(shortToken) == false)\n    }\n\n    @Test\n    func `extracts group ID from JSON string`() {\n        let payload = \"\"\"\n        {\"user\":{\"groupId\":\"98765\"}}\n        \"\"\"\n\n        let groupID = MiniMaxLocalStorageImporter._extractGroupIDForTesting(payload)\n\n        #expect(groupID == \"98765\")\n    }\n\n    @Test\n    func `resolves group ID from JWT claims`() {\n        let token = Self.makeJWT(payload: [\n            \"iss\": \"minimax\",\n            \"group_id\": \"12345\",\n            \"pad\": String(repeating: \"x\", count: 80),\n        ])\n\n        #expect(MiniMaxLocalStorageImporter._isMiniMaxJWTForTesting(token))\n        #expect(MiniMaxLocalStorageImporter._groupIDFromJWTForTesting(token) == \"12345\")\n    }\n\n    @Test\n    func `rejects non mini max JW ts without signal`() {\n        let token = Self.makeJWT(payload: [\n            \"iss\": \"other\",\n            \"pad\": String(repeating: \"y\", count: 80),\n        ])\n\n        #expect(MiniMaxLocalStorageImporter._isMiniMaxJWTForTesting(token) == false)\n    }\n\n    private static func makeJWT(payload: [String: Any]) -> String {\n        let header = [\"alg\": \"none\", \"typ\": \"JWT\"]\n        let headerData = try? JSONSerialization.data(withJSONObject: header)\n        let payloadData = try? JSONSerialization.data(withJSONObject: payload)\n        let headerPart = self.base64URL(headerData ?? Data())\n        let payloadPart = self.base64URL(payloadData ?? Data())\n        let signature = String(repeating: \"s\", count: 32)\n        return \"\\(headerPart).\\(payloadPart).\\(signature)\"\n    }\n\n    private static func base64URL(_ data: Data) -> String {\n        let raw = data.base64EncodedString()\n        return raw\n            .replacingOccurrences(of: \"+\", with: \"-\")\n            .replacingOccurrences(of: \"/\", with: \"_\")\n            .replacingOccurrences(of: \"=\", with: \"\")\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/MiniMaxProviderTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\nstruct MiniMaxCookieHeaderTests {\n    @Test\n    func `normalizes raw cookie header`() {\n        let raw = \"foo=bar; session=abc123\"\n        let normalized = MiniMaxCookieHeader.normalized(from: raw)\n        #expect(normalized == \"foo=bar; session=abc123\")\n    }\n\n    @Test\n    func `extracts from cookie header line`() {\n        let raw = \"Cookie: foo=bar; session=abc123\"\n        let normalized = MiniMaxCookieHeader.normalized(from: raw)\n        #expect(normalized == \"foo=bar; session=abc123\")\n    }\n\n    @Test\n    func `extracts from curl header`() {\n        let raw = \"curl https://platform.minimax.io -H 'Cookie: foo=bar; session=abc123' -H 'accept: */*'\"\n        let normalized = MiniMaxCookieHeader.normalized(from: raw)\n        #expect(normalized == \"foo=bar; session=abc123\")\n    }\n\n    @Test\n    func `extracts from curl cookie flag`() {\n        let raw = \"curl https://platform.minimax.io --cookie 'foo=bar; session=abc123'\"\n        let normalized = MiniMaxCookieHeader.normalized(from: raw)\n        #expect(normalized == \"foo=bar; session=abc123\")\n    }\n\n    @Test\n    func `extracts auth and group ID from curl`() {\n        let raw = \"\"\"\n        curl 'https://platform.minimax.io/v1/api/openplatform/coding_plan/remains?GroupId=123456' \\\n          -H 'authorization: Bearer token123' \\\n          -H 'Cookie: foo=bar; session=abc123'\n        \"\"\"\n        let override = MiniMaxCookieHeader.override(from: raw)\n        #expect(override?.cookieHeader == \"foo=bar; session=abc123\")\n        #expect(override?.authorizationToken == \"token123\")\n        #expect(override?.groupID == \"123456\")\n    }\n\n    @Test\n    func `extracts auth from uppercase header`() {\n        let raw = \"\"\"\n        curl 'https://platform.minimax.io/v1/api/openplatform/coding_plan/remains?GROUP_ID=98765' \\\n          -H 'Authorization: Bearer token-abc' \\\n          -H 'Cookie: foo=bar; session=abc123'\n        \"\"\"\n        let override = MiniMaxCookieHeader.override(from: raw)\n        #expect(override?.authorizationToken == \"token-abc\")\n        #expect(override?.groupID == \"98765\")\n    }\n}\n\nstruct MiniMaxUsageParserTests {\n    @Test\n    func `parses coding plan snapshot`() throws {\n        let now = Date(timeIntervalSince1970: 1_700_000_000)\n        let html = \"\"\"\n        <div>Coding Plan</div>\n        <div>Max</div>\n        <div>Available usage: 1,000 prompts / 5 hours</div>\n        <div>Current Usage</div>\n        <div>0% Used</div>\n        <div>Resets in 4 min</div>\n        \"\"\"\n\n        let snapshot = try MiniMaxUsageParser.parse(html: html, now: now)\n\n        #expect(snapshot.planName == \"Max\")\n        #expect(snapshot.availablePrompts == 1000)\n        #expect(snapshot.windowMinutes == 300)\n        #expect(snapshot.usedPercent == 0)\n        #expect(snapshot.resetsAt == now.addingTimeInterval(240))\n\n        let usage = snapshot.toUsageSnapshot()\n        #expect(usage.primary?.resetDescription == \"1000 prompts / 5 hours\")\n    }\n\n    @Test\n    func `parses coding plan remains response`() throws {\n        let now = Date(timeIntervalSince1970: 1_700_000_000)\n        let start = 1_700_000_000_000\n        let end = start + 5 * 60 * 60 * 1000\n        let json = \"\"\"\n        {\n          \"base_resp\": { \"status_code\": 0 },\n          \"current_subscribe_title\": \"Max\",\n          \"model_remains\": [\n            {\n              \"current_interval_total_count\": 1000,\n              \"current_interval_usage_count\": 250,\n              \"start_time\": \\(start),\n              \"end_time\": \\(end),\n              \"remains_time\": 240000\n            }\n          ]\n        }\n        \"\"\"\n\n        let snapshot = try MiniMaxUsageParser.parseCodingPlanRemains(data: Data(json.utf8), now: now)\n        let expectedReset = Date(timeIntervalSince1970: TimeInterval(end) / 1000)\n\n        #expect(snapshot.planName == \"Max\")\n        #expect(snapshot.availablePrompts == 1000)\n        #expect(snapshot.currentPrompts == 750)\n        #expect(snapshot.remainingPrompts == 250)\n        #expect(snapshot.windowMinutes == 300)\n        #expect(snapshot.usedPercent == 75)\n        #expect(snapshot.resetsAt == expectedReset)\n    }\n\n    @Test\n    func `parses coding plan remains from data wrapper`() throws {\n        let now = Date(timeIntervalSince1970: 1_700_000_100)\n        let start = 1_700_000_000_000\n        let end = start + 5 * 60 * 60 * 1000\n        let json = \"\"\"\n        {\n          \"base_resp\": { \"status_code\": \"0\" },\n          \"data\": {\n            \"current_subscribe_title\": \"Max\",\n            \"model_remains\": [\n              {\n                \"current_interval_total_count\": \"15000\",\n                \"current_interval_usage_count\": \"14989\",\n                \"start_time\": \\(start),\n                \"end_time\": \\(end),\n                \"remains_time\": 8941292\n              }\n            ]\n          }\n        }\n        \"\"\"\n\n        let snapshot = try MiniMaxUsageParser.parseCodingPlanRemains(data: Data(json.utf8), now: now)\n        let expectedUsed = Double(11) / Double(15000) * 100\n        let expectedReset = Date(timeIntervalSince1970: TimeInterval(end) / 1000)\n\n        #expect(snapshot.planName == \"Max\")\n        #expect(snapshot.availablePrompts == 15000)\n        #expect(snapshot.currentPrompts == 11)\n        #expect(snapshot.remainingPrompts == 14989)\n        #expect(snapshot.windowMinutes == 300)\n        #expect(abs((snapshot.usedPercent ?? 0) - expectedUsed) < 0.01)\n        #expect(snapshot.resetsAt == expectedReset)\n    }\n\n    @Test\n    func `parses coding plan from next data`() throws {\n        let now = Date(timeIntervalSince1970: 1_700_000_000)\n        let start = 1_700_000_000_000\n        let end = start + 5 * 60 * 60 * 1000\n        let json = \"\"\"\n        {\n          \"props\": {\n            \"pageProps\": {\n              \"data\": {\n                \"base_resp\": { \"status_code\": 0 },\n                \"current_subscribe_title\": \"Max\",\n                \"model_remains\": [\n                  {\n                    \"current_interval_total_count\": 1000,\n                    \"current_interval_usage_count\": 250,\n                    \"start_time\": \\(start),\n                    \"end_time\": \\(end),\n                    \"remains_time\": 240000\n                  }\n                ]\n              }\n            }\n          }\n        }\n        \"\"\"\n        let html = \"\"\"\n        <html>\n          <script id=\"__NEXT_DATA__\" type=\"application/json\">\\(json)</script>\n        </html>\n        \"\"\"\n\n        let snapshot = try MiniMaxUsageParser.parse(html: html, now: now)\n        let expectedReset = Date(timeIntervalSince1970: TimeInterval(end) / 1000)\n\n        #expect(snapshot.planName == \"Max\")\n        #expect(snapshot.availablePrompts == 1000)\n        #expect(snapshot.currentPrompts == 750)\n        #expect(snapshot.remainingPrompts == 250)\n        #expect(snapshot.windowMinutes == 300)\n        #expect(snapshot.usedPercent == 75)\n        #expect(snapshot.resetsAt == expectedReset)\n    }\n\n    @Test\n    func `parses HTML with used prefix and reset time`() throws {\n        var calendar = Calendar(identifier: .gregorian)\n        calendar.timeZone = TimeZone(identifier: \"UTC\") ?? .current\n        let now = try #require(calendar.date(from: DateComponents(year: 2025, month: 1, day: 1, hour: 10, minute: 0)))\n        let expectedReset = try #require(calendar.date(from: DateComponents(\n            year: 2025,\n            month: 1,\n            day: 1,\n            hour: 23,\n            minute: 30)))\n\n        let html = \"\"\"\n        <div>Coding Plan Pro</div>\n        <div>Available usage: 1,500 prompts / 1.5 hours</div>\n        <div>Used 75%</div>\n        <div>Resets at 23:30 (UTC)</div>\n        \"\"\"\n\n        let snapshot = try MiniMaxUsageParser.parse(html: html, now: now)\n\n        #expect(snapshot.planName == \"Pro\")\n        #expect(snapshot.availablePrompts == 1500)\n        #expect(snapshot.windowMinutes == 90)\n        #expect(snapshot.usedPercent == 75)\n        #expect(snapshot.resetsAt == expectedReset)\n    }\n\n    @Test\n    func `throws on missing cookie response`() {\n        let json = \"\"\"\n        {\n          \"base_resp\": { \"status_code\": 1004, \"status_msg\": \"cookie is missing, log in again\" }\n        }\n        \"\"\"\n\n        #expect(throws: MiniMaxUsageError.invalidCredentials) {\n            try MiniMaxUsageParser.parseCodingPlanRemains(data: Data(json.utf8))\n        }\n    }\n\n    @Test\n    func `throws on string status code when logged out`() {\n        let json = \"\"\"\n        {\n          \"base_resp\": { \"status_code\": \"1004\", \"status_msg\": \"login required\" }\n        }\n        \"\"\"\n\n        #expect(throws: MiniMaxUsageError.invalidCredentials) {\n            try MiniMaxUsageParser.parseCodingPlanRemains(data: Data(json.utf8))\n        }\n    }\n\n    @Test\n    func `throws on error in data wrapper`() {\n        let json = \"\"\"\n        {\n          \"data\": {\n            \"base_resp\": { \"status_code\": 1004, \"status_msg\": \"unauthorized\" }\n          }\n        }\n        \"\"\"\n\n        #expect(throws: MiniMaxUsageError.invalidCredentials) {\n            try MiniMaxUsageParser.parseCodingPlanRemains(data: Data(json.utf8))\n        }\n    }\n}\n\nstruct MiniMaxAPIRegionTests {\n    @Test\n    func `defaults to global hosts`() {\n        let codingPlan = MiniMaxUsageFetcher.resolveCodingPlanURL(region: .global, environment: [:])\n        let remains = MiniMaxUsageFetcher.resolveRemainsURL(region: .global, environment: [:])\n        #expect(codingPlan.host == \"platform.minimax.io\")\n        #expect(remains.host == \"platform.minimax.io\")\n    }\n\n    @Test\n    func `uses china mainland hosts`() {\n        let codingPlan = MiniMaxUsageFetcher.resolveCodingPlanURL(region: .chinaMainland, environment: [:])\n        let remains = MiniMaxUsageFetcher.resolveRemainsURL(region: .chinaMainland, environment: [:])\n        #expect(codingPlan.host == \"platform.minimaxi.com\")\n        #expect(remains.host == \"platform.minimaxi.com\")\n        #expect(codingPlan.query == \"cycle_type=3\")\n    }\n\n    @Test\n    func `host override wins for remains and coding plan`() {\n        let env = [MiniMaxSettingsReader.hostKey: \"api.minimaxi.com\"]\n        let codingPlan = MiniMaxUsageFetcher.resolveCodingPlanURL(region: .global, environment: env)\n        let remains = MiniMaxUsageFetcher.resolveRemainsURL(region: .global, environment: env)\n        #expect(codingPlan.host == \"api.minimaxi.com\")\n        #expect(remains.host == \"api.minimaxi.com\")\n    }\n\n    @Test\n    func `remains url override beats host`() {\n        let env = [MiniMaxSettingsReader.remainsURLKey: \"https://platform.minimaxi.com/custom/remains\"]\n        let remains = MiniMaxUsageFetcher.resolveRemainsURL(region: .global, environment: env)\n        #expect(remains.absoluteString == \"https://platform.minimaxi.com/custom/remains\")\n    }\n\n    @Test\n    func `origin uses coding plan override host`() {\n        let env = [MiniMaxSettingsReader.codingPlanURLKey: \"https://api.minimaxi.com/custom/path?cycle_type=3\"]\n        let codingPlan = MiniMaxUsageFetcher.resolveCodingPlanURL(region: .global, environment: env)\n        let origin = MiniMaxUsageFetcher.originURL(from: codingPlan)\n        #expect(origin.absoluteString == \"https://api.minimaxi.com\")\n    }\n\n    @Test\n    func `origin strips host override path`() {\n        let env = [MiniMaxSettingsReader.hostKey: \"https://api.minimaxi.com/custom/path\"]\n        let codingPlan = MiniMaxUsageFetcher.resolveCodingPlanURL(region: .global, environment: env)\n        let origin = MiniMaxUsageFetcher.originURL(from: codingPlan)\n        #expect(origin.absoluteString == \"https://api.minimaxi.com\")\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/OllamaUsageFetcherRetryMappingTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\n@Suite(.serialized)\nstruct OllamaUsageFetcherRetryMappingTests {\n    @Test\n    func `missing usage shape surfaces public parse failed message`() async {\n        defer { OllamaRetryMappingStubURLProtocol.handler = nil }\n\n        OllamaRetryMappingStubURLProtocol.handler = { request in\n            guard let url = request.url else { throw URLError(.badURL) }\n            let body = \"<html><body>No usage data rendered.</body></html>\"\n            return Self.makeResponse(url: url, body: body, statusCode: 200)\n        }\n\n        let fetcher = OllamaUsageFetcher(\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            makeURLSession: { delegate in\n                let config = URLSessionConfiguration.ephemeral\n                config.protocolClasses = [OllamaRetryMappingStubURLProtocol.self]\n                return URLSession(configuration: config, delegate: delegate, delegateQueue: nil)\n            })\n        do {\n            _ = try await fetcher.fetch(\n                cookieHeaderOverride: \"session=test-cookie\",\n                manualCookieMode: true)\n            Issue.record(\"Expected OllamaUsageError.parseFailed\")\n        } catch let error as OllamaUsageError {\n            guard case let .parseFailed(message) = error else {\n                Issue.record(\"Expected parseFailed, got \\(error)\")\n                return\n            }\n            #expect(message == \"Missing Ollama usage data.\")\n        } catch {\n            Issue.record(\"Expected OllamaUsageError.parseFailed, got \\(error)\")\n        }\n    }\n\n    private static func makeResponse(\n        url: URL,\n        body: String,\n        statusCode: Int) -> (HTTPURLResponse, Data)\n    {\n        let response = HTTPURLResponse(\n            url: url,\n            statusCode: statusCode,\n            httpVersion: \"HTTP/1.1\",\n            headerFields: [\"Content-Type\": \"text/html\"])!\n        return (response, Data(body.utf8))\n    }\n}\n\nfinal class OllamaRetryMappingStubURLProtocol: URLProtocol {\n    nonisolated(unsafe) static var handler: ((URLRequest) throws -> (HTTPURLResponse, Data))?\n\n    override static func canInit(with request: URLRequest) -> Bool {\n        guard let host = request.url?.host?.lowercased() else { return false }\n        return host == \"ollama.com\" || host == \"www.ollama.com\"\n    }\n\n    override static func canonicalRequest(for request: URLRequest) -> URLRequest {\n        request\n    }\n\n    override func startLoading() {\n        guard let handler = Self.handler else {\n            self.client?.urlProtocol(self, didFailWithError: URLError(.badServerResponse))\n            return\n        }\n        do {\n            let (response, data) = try handler(self.request)\n            self.client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)\n            self.client?.urlProtocol(self, didLoad: data)\n            self.client?.urlProtocolDidFinishLoading(self)\n        } catch {\n            self.client?.urlProtocol(self, didFailWithError: error)\n        }\n    }\n\n    override func stopLoading() {}\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/OllamaUsageFetcherTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\nstruct OllamaUsageFetcherTests {\n    @Test\n    func `attaches cookie for ollama hosts`() {\n        #expect(OllamaUsageFetcher.shouldAttachCookie(to: URL(string: \"https://ollama.com/settings\")))\n        #expect(OllamaUsageFetcher.shouldAttachCookie(to: URL(string: \"https://www.ollama.com\")))\n        #expect(OllamaUsageFetcher.shouldAttachCookie(to: URL(string: \"https://app.ollama.com/path\")))\n    }\n\n    @Test\n    func `rejects non ollama hosts`() {\n        #expect(!OllamaUsageFetcher.shouldAttachCookie(to: URL(string: \"https://example.com\")))\n        #expect(!OllamaUsageFetcher.shouldAttachCookie(to: URL(string: \"https://ollama.com.evil.com\")))\n        #expect(!OllamaUsageFetcher.shouldAttachCookie(to: nil))\n    }\n\n    @Test\n    func `manual mode without valid header throws no session cookie`() {\n        do {\n            _ = try OllamaUsageFetcher.resolveManualCookieHeader(\n                override: nil,\n                manualCookieMode: true)\n            Issue.record(\"Expected OllamaUsageError.noSessionCookie\")\n        } catch OllamaUsageError.noSessionCookie {\n            // expected\n        } catch {\n            Issue.record(\"Expected OllamaUsageError.noSessionCookie, got \\(error)\")\n        }\n    }\n\n    @Test\n    func `auto mode without header does not force manual error`() throws {\n        let resolved = try OllamaUsageFetcher.resolveManualCookieHeader(\n            override: nil,\n            manualCookieMode: false)\n        #expect(resolved == nil)\n    }\n\n    @Test\n    func `manual mode without recognized session cookie throws no session cookie`() {\n        do {\n            _ = try OllamaUsageFetcher.resolveManualCookieHeader(\n                override: \"analytics_session_id=noise; theme=dark\",\n                manualCookieMode: true)\n            Issue.record(\"Expected OllamaUsageError.noSessionCookie\")\n        } catch OllamaUsageError.noSessionCookie {\n            // expected\n        } catch {\n            Issue.record(\"Expected OllamaUsageError.noSessionCookie, got \\(error)\")\n        }\n    }\n\n    @Test\n    func `manual mode with recognized session cookie accepts header`() throws {\n        let resolved = try OllamaUsageFetcher.resolveManualCookieHeader(\n            override: \"next-auth.session-token.0=abc; theme=dark\",\n            manualCookieMode: true)\n        #expect(resolved?.contains(\"next-auth.session-token.0=abc\") == true)\n    }\n\n    @Test\n    func `retry policy retries only for auth errors`() {\n        #expect(OllamaUsageFetcher.shouldRetryWithNextCookieCandidate(after: OllamaUsageError.invalidCredentials))\n        #expect(OllamaUsageFetcher.shouldRetryWithNextCookieCandidate(after: OllamaUsageError.notLoggedIn))\n        #expect(OllamaUsageFetcher.shouldRetryWithNextCookieCandidate(\n            after: OllamaUsageFetcher.RetryableParseFailure.missingUsageData))\n        #expect(!OllamaUsageFetcher.shouldRetryWithNextCookieCandidate(\n            after: OllamaUsageError.parseFailed(\"Missing Ollama usage data.\")))\n        #expect(!OllamaUsageFetcher.shouldRetryWithNextCookieCandidate(\n            after: OllamaUsageError.parseFailed(\"Unexpected parser mismatch.\")))\n        #expect(!OllamaUsageFetcher.shouldRetryWithNextCookieCandidate(after: OllamaUsageError.networkError(\"timeout\")))\n    }\n\n    #if os(macOS)\n    @Test\n    func `cookie importer defaults to chrome first`() {\n        #expect(OllamaCookieImporter.defaultPreferredBrowsers == [.chrome])\n    }\n\n    @Test\n    func `cookie selector skips session like noise and finds recognized cookie`() throws {\n        let first = OllamaCookieImporter.SessionInfo(\n            cookies: [Self.makeCookie(name: \"analytics_session_id\", value: \"noise\")],\n            sourceLabel: \"Profile A\")\n        let second = OllamaCookieImporter.SessionInfo(\n            cookies: [Self.makeCookie(name: \"__Secure-next-auth.session-token\", value: \"auth\")],\n            sourceLabel: \"Profile B\")\n\n        let selected = try OllamaCookieImporter.selectSessionInfo(from: [first, second])\n        #expect(selected.sourceLabel == \"Profile B\")\n    }\n\n    @Test\n    func `cookie selector throws when no recognized session cookie exists`() {\n        let candidates = [\n            OllamaCookieImporter.SessionInfo(\n                cookies: [Self.makeCookie(name: \"analytics_session_id\", value: \"noise\")],\n                sourceLabel: \"Profile A\"),\n            OllamaCookieImporter.SessionInfo(\n                cookies: [Self.makeCookie(name: \"tracking_session\", value: \"noise\")],\n                sourceLabel: \"Profile B\"),\n        ]\n\n        do {\n            _ = try OllamaCookieImporter.selectSessionInfo(from: candidates)\n            Issue.record(\"Expected OllamaUsageError.noSessionCookie\")\n        } catch OllamaUsageError.noSessionCookie {\n            // expected\n        } catch {\n            Issue.record(\"Expected OllamaUsageError.noSessionCookie, got \\(error)\")\n        }\n    }\n\n    @Test\n    func `cookie selector accepts chunked next auth session token cookie`() throws {\n        let candidate = OllamaCookieImporter.SessionInfo(\n            cookies: [Self.makeCookie(name: \"next-auth.session-token.0\", value: \"chunk0\")],\n            sourceLabel: \"Profile C\")\n\n        let selected = try OllamaCookieImporter.selectSessionInfo(from: [candidate])\n        #expect(selected.sourceLabel == \"Profile C\")\n    }\n\n    @Test\n    func `cookie selector keeps recognized candidates in order`() throws {\n        let first = OllamaCookieImporter.SessionInfo(\n            cookies: [Self.makeCookie(name: \"session\", value: \"stale\")],\n            sourceLabel: \"Chrome Profile A\")\n        let second = OllamaCookieImporter.SessionInfo(\n            cookies: [Self.makeCookie(name: \"next-auth.session-token.0\", value: \"valid\")],\n            sourceLabel: \"Chrome Profile B\")\n        let noise = OllamaCookieImporter.SessionInfo(\n            cookies: [Self.makeCookie(name: \"analytics_session_id\", value: \"noise\")],\n            sourceLabel: \"Chrome Profile C\")\n\n        let selected = try OllamaCookieImporter.selectSessionInfos(from: [first, noise, second])\n        #expect(selected.map(\\.sourceLabel) == [\"Chrome Profile A\", \"Chrome Profile B\"])\n    }\n\n    @Test\n    func `cookie selector does not fallback when fallback disabled`() {\n        let preferred = [\n            OllamaCookieImporter.SessionInfo(\n                cookies: [Self.makeCookie(name: \"analytics_session_id\", value: \"noise\")],\n                sourceLabel: \"Chrome Profile\"),\n        ]\n        let fallback = [\n            OllamaCookieImporter.SessionInfo(\n                cookies: [Self.makeCookie(name: \"next-auth.session-token.0\", value: \"chunk0\")],\n                sourceLabel: \"Safari Profile\"),\n        ]\n\n        do {\n            _ = try OllamaCookieImporter.selectSessionInfoWithFallback(\n                preferredCandidates: preferred,\n                allowFallbackBrowsers: false,\n                loadFallbackCandidates: { fallback })\n            Issue.record(\"Expected OllamaUsageError.noSessionCookie\")\n        } catch OllamaUsageError.noSessionCookie {\n            // expected\n        } catch {\n            Issue.record(\"Expected OllamaUsageError.noSessionCookie, got \\(error)\")\n        }\n    }\n\n    @Test\n    func `cookie selector falls back to non chrome candidate when fallback enabled`() throws {\n        let preferred = [\n            OllamaCookieImporter.SessionInfo(\n                cookies: [Self.makeCookie(name: \"analytics_session_id\", value: \"noise\")],\n                sourceLabel: \"Chrome Profile\"),\n        ]\n        let fallback = [\n            OllamaCookieImporter.SessionInfo(\n                cookies: [Self.makeCookie(name: \"next-auth.session-token.0\", value: \"chunk0\")],\n                sourceLabel: \"Safari Profile\"),\n        ]\n\n        let selected = try OllamaCookieImporter.selectSessionInfoWithFallback(\n            preferredCandidates: preferred,\n            allowFallbackBrowsers: true,\n            loadFallbackCandidates: { fallback })\n        #expect(selected.sourceLabel == \"Safari Profile\")\n    }\n\n    private static func makeCookie(\n        name: String,\n        value: String,\n        domain: String = \"ollama.com\") -> HTTPCookie\n    {\n        HTTPCookie(\n            properties: [\n                .name: name,\n                .value: value,\n                .domain: domain,\n                .path: \"/\",\n            ])!\n    }\n    #endif\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/OllamaUsageParserTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\nstruct OllamaUsageParserTests {\n    @Test\n    func `parses cloud usage from settings HTML`() throws {\n        let now = Date(timeIntervalSince1970: 1_700_000_000)\n        let html = \"\"\"\n        <div>\n          <h2 class=\\\"text-xl\\\">\n            <span>Cloud Usage</span>\n            <span class=\\\"text-xs\\\">free</span>\n          </h2>\n          <h2 id=\\\"header-email\\\">user@example.com</h2>\n          <div>\n            <span>Session usage</span>\n            <span>0.1% used</span>\n            <div class=\\\"local-time\\\" data-time=\\\"2026-01-30T18:00:00Z\\\">Resets in 3 hours</div>\n          </div>\n          <div>\n            <span>Weekly usage</span>\n            <span>0.7% used</span>\n            <div class=\\\"local-time\\\" data-time=\\\"2026-02-02T00:00:00Z\\\">Resets in 2 days</div>\n          </div>\n        </div>\n        \"\"\"\n\n        let snapshot = try OllamaUsageParser.parse(html: html, now: now)\n\n        #expect(snapshot.planName == \"free\")\n        #expect(snapshot.accountEmail == \"user@example.com\")\n        #expect(snapshot.sessionUsedPercent == 0.1)\n        #expect(snapshot.weeklyUsedPercent == 0.7)\n\n        let formatter = ISO8601DateFormatter()\n        formatter.formatOptions = [.withInternetDateTime]\n        let expectedSession = formatter.date(from: \"2026-01-30T18:00:00Z\")\n        let expectedWeekly = formatter.date(from: \"2026-02-02T00:00:00Z\")\n        #expect(snapshot.sessionResetsAt == expectedSession)\n        #expect(snapshot.weeklyResetsAt == expectedWeekly)\n\n        let usage = snapshot.toUsageSnapshot()\n        #expect(usage.identity?.loginMethod == \"free\")\n        #expect(usage.identity?.accountEmail == \"user@example.com\")\n    }\n\n    @Test\n    func `missing usage throws parse failed`() {\n        let html = \"<html><body>No usage here. login status unknown.</body></html>\"\n\n        #expect {\n            try OllamaUsageParser.parse(html: html)\n        } throws: { error in\n            guard case let OllamaUsageError.parseFailed(message) = error else { return false }\n            return message.contains(\"Missing Ollama usage data\")\n        }\n    }\n\n    @Test\n    func `classified parse missing usage returns typed failure`() {\n        let html = \"<html><body>No usage here. login status unknown.</body></html>\"\n        let result = OllamaUsageParser.parseClassified(html: html)\n\n        switch result {\n        case .success:\n            Issue.record(\"Expected classified parse failure for missing usage data\")\n        case let .failure(failure):\n            #expect(failure == .missingUsageData)\n        }\n    }\n\n    @Test\n    func `signed out throws not logged in`() {\n        let html = \"\"\"\n        <html>\n          <body>\n            <h1>Sign in to Ollama</h1>\n            <form action=\"/auth/signin\" method=\"post\">\n              <input type=\"email\" name=\"email\" />\n              <input type=\"password\" name=\"password\" />\n            </form>\n          </body>\n        </html>\n        \"\"\"\n\n        #expect {\n            try OllamaUsageParser.parse(html: html)\n        } throws: { error in\n            guard case OllamaUsageError.notLoggedIn = error else { return false }\n            return true\n        }\n    }\n\n    @Test\n    func `classified parse signed out returns typed failure`() {\n        let html = \"\"\"\n        <html>\n          <body>\n            <h1>Sign in to Ollama</h1>\n            <form action=\"/auth/signin\" method=\"post\">\n              <input type=\"email\" name=\"email\" />\n              <input type=\"password\" name=\"password\" />\n            </form>\n          </body>\n        </html>\n        \"\"\"\n\n        let result = OllamaUsageParser.parseClassified(html: html)\n        switch result {\n        case .success:\n            Issue.record(\"Expected classified parse failure for signed-out HTML\")\n        case let .failure(failure):\n            #expect(failure == .notLoggedIn)\n        }\n    }\n\n    @Test\n    func `generic sign in text without auth markers throws parse failed`() {\n        let html = \"\"\"\n        <html>\n          <body>\n            <h2>Usage Dashboard</h2>\n            <p>If you have an account, you can sign in from the homepage.</p>\n            <div>No usage rows rendered.</div>\n          </body>\n        </html>\n        \"\"\"\n\n        #expect {\n            try OllamaUsageParser.parse(html: html)\n        } throws: { error in\n            guard case let OllamaUsageError.parseFailed(message) = error else { return false }\n            return message.contains(\"Missing Ollama usage data\")\n        }\n    }\n\n    @Test\n    func `parses hourly usage as primary window`() throws {\n        let now = Date(timeIntervalSince1970: 1_700_000_000)\n        let html = \"\"\"\n        <div>\n          <span>Hourly usage</span>\n          <span>2.5% used</span>\n          <div class=\\\"local-time\\\" data-time=\\\"2026-01-30T18:00:00Z\\\">Resets in 3 hours</div>\n          <span>Weekly usage</span>\n          <span>4.2% used</span>\n          <div class=\\\"local-time\\\" data-time=\\\"2026-02-02T00:00:00Z\\\">Resets in 2 days</div>\n        </div>\n        \"\"\"\n\n        let snapshot = try OllamaUsageParser.parse(html: html, now: now)\n\n        #expect(snapshot.sessionUsedPercent == 2.5)\n        #expect(snapshot.weeklyUsedPercent == 4.2)\n    }\n\n    @Test\n    func `parses usage when used is capitalized`() throws {\n        let now = Date(timeIntervalSince1970: 1_700_000_000)\n        let html = \"\"\"\n        <div>\n          <span>Session usage</span>\n          <span>1.2% Used</span>\n          <div class=\\\"local-time\\\" data-time=\\\"2026-01-30T18:00:00Z\\\">Resets in 3 hours</div>\n          <span>Weekly usage</span>\n          <span>3.4% USED</span>\n          <div class=\\\"local-time\\\" data-time=\\\"2026-02-02T00:00:00Z\\\">Resets in 2 days</div>\n        </div>\n        \"\"\"\n\n        let snapshot = try OllamaUsageParser.parse(html: html, now: now)\n\n        #expect(snapshot.sessionUsedPercent == 1.2)\n        #expect(snapshot.weeklyUsedPercent == 3.4)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/OpenAIDashboardBrowserCookieImporterTests.swift",
    "content": "import CodexBarCore\nimport Testing\n\nstruct OpenAIDashboardBrowserCookieImporterTests {\n    @Test\n    func `mismatch error mentions source label`() {\n        let err = OpenAIDashboardBrowserCookieImporter.ImportError.noMatchingAccount(\n            found: [\n                .init(sourceLabel: \"Safari\", email: \"a@example.com\"),\n                .init(sourceLabel: \"Chrome\", email: \"b@example.com\"),\n            ])\n        let msg = err.localizedDescription\n        #expect(msg.contains(\"Safari=a@example.com\"))\n        #expect(msg.contains(\"Chrome=b@example.com\"))\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/OpenAIDashboardFetcherCreditsWaitTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\nstruct OpenAIDashboardFetcherCreditsWaitTests {\n    @Test\n    func `waits after scroll request`() {\n        let now = Date()\n        let shouldWait = OpenAIDashboardFetcher.shouldWaitForCreditsHistory(.init(\n            now: now,\n            anyDashboardSignalAt: now.addingTimeInterval(-10),\n            creditsHeaderVisibleAt: nil,\n            creditsHeaderPresent: false,\n            creditsHeaderInViewport: false,\n            didScrollToCredits: true))\n        #expect(shouldWait == true)\n    }\n\n    @Test\n    func `waits briefly when header visible but table empty`() {\n        let now = Date()\n        let visibleAt = now.addingTimeInterval(-1.0)\n        let shouldWait = OpenAIDashboardFetcher.shouldWaitForCreditsHistory(.init(\n            now: now,\n            anyDashboardSignalAt: now.addingTimeInterval(-10),\n            creditsHeaderVisibleAt: visibleAt,\n            creditsHeaderPresent: true,\n            creditsHeaderInViewport: true,\n            didScrollToCredits: false))\n        #expect(shouldWait == true)\n    }\n\n    @Test\n    func `stops waiting after header has been visible long enough`() {\n        let now = Date()\n        let visibleAt = now.addingTimeInterval(-3.0)\n        let shouldWait = OpenAIDashboardFetcher.shouldWaitForCreditsHistory(.init(\n            now: now,\n            anyDashboardSignalAt: now.addingTimeInterval(-10),\n            creditsHeaderVisibleAt: visibleAt,\n            creditsHeaderPresent: true,\n            creditsHeaderInViewport: true,\n            didScrollToCredits: false))\n        #expect(shouldWait == false)\n    }\n\n    @Test\n    func `waits briefly after first dashboard signal even when header not present yet`() {\n        let now = Date()\n        let startedAt = now.addingTimeInterval(-2.0)\n        let shouldWait = OpenAIDashboardFetcher.shouldWaitForCreditsHistory(.init(\n            now: now,\n            anyDashboardSignalAt: startedAt,\n            creditsHeaderVisibleAt: nil,\n            creditsHeaderPresent: false,\n            creditsHeaderInViewport: false,\n            didScrollToCredits: false))\n        #expect(shouldWait == true)\n    }\n\n    @Test\n    func `stops waiting eventually when header never appears`() {\n        let now = Date()\n        let startedAt = now.addingTimeInterval(-7.0)\n        let shouldWait = OpenAIDashboardFetcher.shouldWaitForCreditsHistory(.init(\n            now: now,\n            anyDashboardSignalAt: startedAt,\n            creditsHeaderVisibleAt: nil,\n            creditsHeaderPresent: false,\n            creditsHeaderInViewport: false,\n            didScrollToCredits: false))\n        #expect(shouldWait == false)\n    }\n\n    @Test\n    func `sanitized timeout preserves positive caller deadline`() {\n        #expect(OpenAIDashboardFetcher.sanitizedTimeout(60) == 60)\n        #expect(OpenAIDashboardFetcher.sanitizedTimeout(25) == 25)\n        #expect(OpenAIDashboardFetcher.sanitizedTimeout(0.5) == 0.5)\n    }\n\n    @Test\n    func `sanitized timeout falls back for invalid values`() {\n        #expect(OpenAIDashboardFetcher.sanitizedTimeout(0) == 1)\n        #expect(OpenAIDashboardFetcher.sanitizedTimeout(-5) == 1)\n        #expect(OpenAIDashboardFetcher.sanitizedTimeout(.infinity) == 1)\n        #expect(OpenAIDashboardFetcher.sanitizedTimeout(.nan) == 1)\n    }\n\n    @Test\n    func `deadline starts at call start and remaining timeout shrinks from there`() {\n        let start = Date(timeIntervalSinceReferenceDate: 1000)\n        let deadline = OpenAIDashboardFetcher.deadline(startingAt: start, timeout: 15)\n\n        #expect(deadline.timeIntervalSince(start) == 15)\n\n        let remaining = OpenAIDashboardFetcher.remainingTimeout(\n            until: deadline,\n            now: start.addingTimeInterval(14.5))\n        #expect(remaining == 0.5)\n    }\n\n    @Test\n    func `remaining timeout does not go negative`() {\n        let deadline = Date(timeIntervalSinceReferenceDate: 2000)\n        let remaining = OpenAIDashboardFetcher.remainingTimeout(\n            until: deadline,\n            now: deadline.addingTimeInterval(3))\n        #expect(remaining == 0)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/OpenAIDashboardNavigationDelegateTests.swift",
    "content": "import Foundation\nimport Testing\nimport WebKit\n@testable import CodexBarCore\n\nstruct OpenAIDashboardNavigationDelegateTests {\n    @Test\n    func `ignores NSURLErrorCancelled`() {\n        let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorCancelled)\n        #expect(NavigationDelegate.shouldIgnoreNavigationError(error))\n    }\n\n    @Test\n    func `does not ignore non-cancelled URL errors`() {\n        let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorTimedOut)\n        #expect(!NavigationDelegate.shouldIgnoreNavigationError(error))\n    }\n\n    @MainActor\n    @Test\n    func `cancelled failure is ignored until finish`() {\n        let webView = WKWebView()\n        var result: Result<Void, Error>?\n        let delegate = NavigationDelegate { result = $0 }\n\n        delegate.webView(webView, didFail: nil, withError: NSError(domain: NSURLErrorDomain, code: NSURLErrorCancelled))\n        #expect(result == nil)\n        delegate.webView(webView, didFinish: nil)\n\n        switch result {\n        case .success?:\n            #expect(Bool(true))\n        default:\n            #expect(Bool(false))\n        }\n    }\n\n    @MainActor\n    @Test\n    func `cancelled provisional failure is ignored until real failure`() {\n        let webView = WKWebView()\n        var result: Result<Void, Error>?\n        let delegate = NavigationDelegate { result = $0 }\n\n        delegate.webView(\n            webView,\n            didFailProvisionalNavigation: nil,\n            withError: NSError(domain: NSURLErrorDomain, code: NSURLErrorCancelled))\n        #expect(result == nil)\n\n        let timeout = NSError(domain: NSURLErrorDomain, code: NSURLErrorTimedOut)\n        delegate.webView(webView, didFailProvisionalNavigation: nil, withError: timeout)\n\n        switch result {\n        case let .failure(error as NSError)?:\n            #expect(error.domain == NSURLErrorDomain)\n            #expect(error.code == NSURLErrorTimedOut)\n        default:\n            #expect(Bool(false))\n        }\n    }\n\n    @Test\n    func `navigation timeout fails with timed out error`() async {\n        final class DelegateBox: @unchecked Sendable {\n            var delegate: NavigationDelegate?\n        }\n\n        let result = await withCheckedContinuation { (continuation: CheckedContinuation<Result<Void, Error>, Never>) in\n            Task { @MainActor in\n                let box = DelegateBox()\n                box.delegate = NavigationDelegate { result in\n                    continuation.resume(returning: result)\n                    box.delegate = nil\n                }\n                box.delegate?.armTimeout(seconds: 0.01)\n            }\n        }\n\n        switch result {\n        case let .failure(error as URLError):\n            #expect(error.code == .timedOut)\n        default:\n            #expect(Bool(false))\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/OpenAIDashboardOffscreenHostTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\nstruct OpenAIDashboardOffscreenHostTests {\n    @Test\n    func `offscreen host frame only intersects by A sliver`() {\n        let visibleFrame = CGRect(x: 0, y: 0, width: 1000, height: 800)\n        let frame = OpenAIDashboardFetcher.offscreenHostWindowFrame(for: visibleFrame)\n        let intersection = frame.intersection(visibleFrame)\n\n        #expect(frame.size.width == visibleFrame.size.width)\n        #expect(frame.size.height == visibleFrame.size.height)\n        #expect(intersection.size.width <= 1.0)\n        #expect(intersection.size.height <= 1.0)\n        #expect(intersection.minX >= visibleFrame.maxX - 1.0)\n        #expect(intersection.minY >= visibleFrame.maxY - 1.0)\n    }\n\n    @Test\n    func `offscreen host alpha value is non zero but tiny`() {\n        let alpha = OpenAIDashboardFetcher.offscreenHostAlphaValue()\n        #expect(alpha > 0)\n        #expect(alpha <= 0.001)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/OpenAIDashboardParserTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n@testable import CodexBar\n\nstruct OpenAIDashboardParserTests {\n    @Test\n    func `parses signed in email from client bootstrap HTML`() {\n        let html = \"\"\"\n        <html>\n        <head></head>\n        <body>\n        <script type=\"application/json\" id=\"client-bootstrap\">\n        {\"authStatus\":\"logged_in\",\"session\":{\"user\":{\"email\":\"studpete@gmail.com\"}}}\n        </script>\n        </body>\n        </html>\n        \"\"\"\n        #expect(OpenAIDashboardParser.parseSignedInEmailFromClientBootstrap(html: html) == \"studpete@gmail.com\")\n        #expect(OpenAIDashboardParser.parseAuthStatusFromClientBootstrap(html: html) == \"logged_in\")\n    }\n\n    @Test\n    func `parses code review remaining percent inline`() {\n        let body = \"Balance\\nCode review 42% remaining\\nCredits remaining 291\"\n        #expect(OpenAIDashboardParser.parseCodeReviewRemainingPercent(bodyText: body) == 42)\n    }\n\n    @Test\n    func `parses code review remaining percent multiline`() {\n        let body = \"Balance\\nCode review\\n100% remaining\\nWeekly usage limit\\n0% remaining\"\n        #expect(OpenAIDashboardParser.parseCodeReviewRemainingPercent(bodyText: body) == 100)\n    }\n\n    @Test\n    func `parses credits remaining`() {\n        let body = \"Balance\\nCredits remaining 1,234.56\\nUsage\"\n        let value = OpenAIDashboardParser.parseCreditsRemaining(bodyText: body)\n        #expect(abs((value ?? 0) - 1234.56) < 0.001)\n    }\n\n    @Test\n    func `parses rate limits`() {\n        let body = \"\"\"\n        Usage limits\n        5h limit\n        72% remaining\n        Resets today at 2:15 PM\n        Weekly limit\n        41% remaining\n        Resets Fri at 9:00 AM\n        \"\"\"\n        let limits = OpenAIDashboardParser.parseRateLimits(bodyText: body)\n        #expect(abs((limits.primary?.usedPercent ?? 0) - 28) < 0.001)\n        #expect(limits.primary?.windowMinutes == 300)\n        #expect(limits.primary?.resetDescription?.lowercased().contains(\"resets\") == true)\n        #expect(abs((limits.secondary?.usedPercent ?? 0) - 59) < 0.001)\n        #expect(limits.secondary?.windowMinutes == 10080)\n    }\n\n    @Test\n    func `parses plan from client bootstrap`() {\n        let html = \"\"\"\n        <html>\n        <body>\n        <script type=\"application/json\" id=\"client-bootstrap\">\n        {\"session\":{\"user\":{\"email\":\"user@example.com\"}},\"planType\":\"plus\"}\n        </script>\n        </body>\n        </html>\n        \"\"\"\n        #expect(OpenAIDashboardParser.parsePlanFromHTML(html: html) == \"Plus\")\n    }\n\n    @Test\n    func `parses credit events from table rows`() {\n        let rows: [[String]] = [\n            [\"Dec 18, 2025\", \"CLI\", \"397.205 credits\"],\n            [\"Dec 17, 2025\", \"GitHub Code Review\", \"506.235 credits\"],\n        ]\n        let events = OpenAIDashboardParser.parseCreditEvents(rows: rows)\n        #expect(events.count == 2)\n        #expect(events.first?.service == \"CLI\")\n        #expect(abs((events.first?.creditsUsed ?? 0) - 397.205) < 0.0001)\n        #expect(events.last?.service == \"GitHub Code Review\")\n        #expect(abs((events.last?.creditsUsed ?? 0) - 506.235) < 0.0001)\n    }\n\n    @Test\n    func `builds daily breakdown from events`() throws {\n        let calendar = Calendar(identifier: .gregorian)\n        var components = DateComponents()\n        components.calendar = calendar\n        components.timeZone = TimeZone(secondsFromGMT: 0)\n\n        components.year = 2025\n        components.month = 12\n        components.day = 18\n        let dec18 = try #require(components.date)\n\n        components.day = 17\n        let dec17 = try #require(components.date)\n\n        let events = [\n            CreditEvent(date: dec18, service: \"CLI\", creditsUsed: 10),\n            CreditEvent(date: dec18, service: \"CLI\", creditsUsed: 5),\n            CreditEvent(date: dec18, service: \"GitHub Code Review\", creditsUsed: 2),\n            CreditEvent(date: dec17, service: \"CLI\", creditsUsed: 1),\n        ]\n\n        let breakdown = OpenAIDashboardSnapshot.makeDailyBreakdown(from: events, maxDays: 30)\n        #expect(breakdown.count == 2)\n        #expect(breakdown.first?.services.first?.service == \"CLI\")\n        #expect(abs((breakdown.first?.services.first?.creditsUsed ?? 0) - 15) < 0.0001)\n    }\n\n    @Test\n    func `decodes snapshot without usage breakdown field`() throws {\n        let json = \"\"\"\n        {\n          \"signedInEmail\": \"user@example.com\",\n          \"codeReviewRemainingPercent\": 42,\n          \"creditEvents\": [],\n          \"dailyBreakdown\": [],\n          \"updatedAt\": \"2025-12-18T00:00:00Z\"\n        }\n        \"\"\"\n        let decoder = JSONDecoder()\n        decoder.dateDecodingStrategy = .iso8601\n        let snapshot = try decoder.decode(OpenAIDashboardSnapshot.self, from: Data(json.utf8))\n        #expect(snapshot.usageBreakdown.isEmpty)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/OpenAIDashboardWebViewCacheTests.swift",
    "content": "import Foundation\nimport Testing\nimport WebKit\n@testable import CodexBar\n@testable import CodexBarCore\n\n/// Tests for OpenAIDashboardWebViewCache to verify WebView reuse behavior.\n///\n/// Background: The cache should keep WebViews alive after use to avoid re-downloading\n/// the ChatGPT SPA bundle on every refresh. Previously, WebViews were destroyed after\n/// each fetch, causing 15+ GB of network traffic over time. See GitHub issues #269, #251.\n@MainActor\n@Suite(.serialized)\nstruct OpenAIDashboardWebViewCacheTests {\n    // MARK: - Data Store Identity Tests\n\n    @Test\n    func `WKWebsiteDataStore should return same instance for same email`() {\n        OpenAIDashboardWebsiteDataStore.clearCacheForTesting()\n\n        let store1 = OpenAIDashboardWebsiteDataStore.store(forAccountEmail: \"test@example.com\")\n        let store2 = OpenAIDashboardWebsiteDataStore.store(forAccountEmail: \"test@example.com\")\n        let store3 = OpenAIDashboardWebsiteDataStore.store(forAccountEmail: \"TEST@EXAMPLE.COM\") // Case insensitive\n\n        #expect(store1 === store2, \"Same email should return same instance\")\n        #expect(store1 === store3, \"Email comparison should be case-insensitive\")\n\n        // Different email should return different instance\n        let store4 = OpenAIDashboardWebsiteDataStore.store(forAccountEmail: \"other@example.com\")\n        #expect(store1 !== store4, \"Different emails should return different instances\")\n\n        OpenAIDashboardWebsiteDataStore.clearCacheForTesting()\n    }\n\n    // MARK: - WebView Reuse Tests\n\n    @Test\n    func `WebView should be cached after release, not destroyed`() async throws {\n        let cache = OpenAIDashboardWebViewCache()\n        let store = WKWebsiteDataStore.nonPersistent()\n        let url = try #require(URL(string: \"about:blank\"))\n\n        // First acquire\n        let lease1 = try await cache.acquire(\n            websiteDataStore: store,\n            usageURL: url,\n            logger: nil)\n        let webView1 = lease1.webView\n\n        // Release - should hide, not destroy\n        lease1.release()\n\n        // Entry should still be in cache\n        #expect(cache.hasCachedEntry(for: store), \"WebView should remain cached after release\")\n        #expect(cache.entryCount == 1, \"Should have exactly one cached entry\")\n\n        // Second acquire should reuse the same WebView\n        let lease2 = try await cache.acquire(\n            websiteDataStore: store,\n            usageURL: url,\n            logger: nil)\n        let webView2 = lease2.webView\n\n        #expect(webView1 === webView2, \"Should reuse the same WebView instance\")\n\n        lease2.release()\n        cache.clearAllForTesting()\n    }\n\n    @Test\n    func `Different data stores should have separate cached WebViews`() async throws {\n        let cache = OpenAIDashboardWebViewCache()\n        let store1 = WKWebsiteDataStore.nonPersistent()\n        let store2 = WKWebsiteDataStore.nonPersistent()\n        let url = try #require(URL(string: \"about:blank\"))\n\n        // Acquire for first store\n        let lease1 = try await cache.acquire(\n            websiteDataStore: store1,\n            usageURL: url,\n            logger: nil)\n        let webView1 = lease1.webView\n        lease1.release()\n\n        // Acquire for second store\n        let lease2 = try await cache.acquire(\n            websiteDataStore: store2,\n            usageURL: url,\n            logger: nil)\n        let webView2 = lease2.webView\n        lease2.release()\n\n        #expect(webView1 !== webView2, \"Different data stores should have different WebViews\")\n        #expect(cache.entryCount == 2, \"Should have two cached entries\")\n\n        cache.clearAllForTesting()\n    }\n\n    // MARK: - Idle Timeout / Pruning Tests\n\n    @Test\n    func `WebView should be pruned after idle timeout`() async throws {\n        let cache = OpenAIDashboardWebViewCache()\n        let store = WKWebsiteDataStore.nonPersistent()\n        let url = try #require(URL(string: \"about:blank\"))\n\n        // Acquire and release\n        let lease = try await cache.acquire(\n            websiteDataStore: store,\n            usageURL: url,\n            logger: nil)\n        lease.release()\n\n        #expect(cache.hasCachedEntry(for: store), \"Should be cached immediately after release\")\n\n        // Simulate time passing beyond idle timeout (10 minutes + buffer)\n        let futureTime = Date().addingTimeInterval(11 * 60)\n        cache.pruneForTesting(now: futureTime)\n\n        #expect(!cache.hasCachedEntry(for: store), \"Should be pruned after idle timeout\")\n        #expect(cache.entryCount == 0, \"Should have no cached entries after prune\")\n    }\n\n    @Test\n    func `Recently used WebView should not be pruned`() async throws {\n        let cache = OpenAIDashboardWebViewCache()\n        let store = WKWebsiteDataStore.nonPersistent()\n        let url = try #require(URL(string: \"about:blank\"))\n\n        // Acquire and release\n        let lease = try await cache.acquire(\n            websiteDataStore: store,\n            usageURL: url,\n            logger: nil)\n        lease.release()\n\n        // Simulate time passing within idle timeout (5 minutes)\n        let nearFutureTime = Date().addingTimeInterval(5 * 60)\n        cache.pruneForTesting(now: nearFutureTime)\n\n        #expect(cache.hasCachedEntry(for: store), \"Should still be cached within idle timeout\")\n    }\n\n    // MARK: - Eviction Tests\n\n    @Test\n    func `Evict should remove specific WebView from cache`() async throws {\n        let cache = OpenAIDashboardWebViewCache()\n        let store1 = WKWebsiteDataStore.nonPersistent()\n        let store2 = WKWebsiteDataStore.nonPersistent()\n        let url = try #require(URL(string: \"about:blank\"))\n\n        // Cache two WebViews\n        let lease1 = try await cache.acquire(websiteDataStore: store1, usageURL: url, logger: nil)\n        lease1.release()\n        let lease2 = try await cache.acquire(websiteDataStore: store2, usageURL: url, logger: nil)\n        lease2.release()\n\n        #expect(cache.entryCount == 2, \"Should have two cached entries\")\n\n        // Evict only the first one\n        cache.evict(websiteDataStore: store1)\n\n        #expect(!cache.hasCachedEntry(for: store1), \"First store should be evicted\")\n        #expect(cache.hasCachedEntry(for: store2), \"Second store should still be cached\")\n        #expect(cache.entryCount == 1, \"Should have one cached entry remaining\")\n\n        cache.clearAllForTesting()\n    }\n\n    // MARK: - Busy WebView Tests\n\n    @Test\n    func `Busy WebView should create temporary WebView for concurrent access`() async throws {\n        let cache = OpenAIDashboardWebViewCache()\n        let store = WKWebsiteDataStore.nonPersistent()\n        let url = try #require(URL(string: \"about:blank\"))\n\n        var logMessages: [String] = []\n        let logger: (String) -> Void = { logMessages.append($0) }\n\n        // Acquire first (don't release yet - keeps it busy)\n        let lease1 = try await cache.acquire(\n            websiteDataStore: store,\n            usageURL: url,\n            logger: logger)\n        let webView1 = lease1.webView\n\n        // Try to acquire again while first is busy\n        let lease2 = try await cache.acquire(\n            websiteDataStore: store,\n            usageURL: url,\n            logger: logger)\n        let webView2 = lease2.webView\n\n        #expect(webView1 !== webView2, \"Should create temporary WebView when cached one is busy\")\n        #expect(\n            logMessages.contains { $0.contains(\"Cached WebView busy\") },\n            \"Should log that cached WebView is busy\")\n\n        lease1.release()\n        lease2.release()\n        cache.clearAllForTesting()\n    }\n\n    // MARK: - Network Traffic Regression Prevention\n\n    @Test\n    func `Multiple sequential fetches should reuse same WebView (network optimization)`() async throws {\n        let cache = OpenAIDashboardWebViewCache()\n        let store = WKWebsiteDataStore.nonPersistent()\n        let url = try #require(URL(string: \"about:blank\"))\n\n        var webViews: [WKWebView] = []\n\n        // Simulate 5 sequential fetches (like 5 refresh cycles)\n        for _ in 0..<5 {\n            let lease = try await cache.acquire(\n                websiteDataStore: store,\n                usageURL: url,\n                logger: nil)\n            webViews.append(lease.webView)\n            lease.release()\n        }\n\n        // All should be the same WebView instance\n        let firstWebView = webViews[0]\n        for (index, webView) in webViews.enumerated() {\n            #expect(\n                webView === firstWebView,\n                \"Fetch \\(index + 1) should reuse the same WebView instance\")\n        }\n\n        // Only one entry should exist in cache\n        #expect(cache.entryCount == 1, \"Should maintain single cached entry across all fetches\")\n\n        cache.clearAllForTesting()\n    }\n\n    // MARK: - Integration Test with Real Data Store Factory\n\n    @Test\n    func `Sequential fetches with OpenAIDashboardWebsiteDataStore should reuse WebView`() async throws {\n        OpenAIDashboardWebsiteDataStore.clearCacheForTesting()\n        let cache = OpenAIDashboardWebViewCache()\n        let url = try #require(URL(string: \"about:blank\"))\n        let email = \"integration-test@example.com\"\n\n        var webViews: [WKWebView] = []\n\n        // Simulate 3 sequential fetches using the real data store factory\n        // This tests that OpenAIDashboardWebsiteDataStore returns stable instances\n        for _ in 0..<3 {\n            let store = OpenAIDashboardWebsiteDataStore.store(forAccountEmail: email)\n            let lease = try await cache.acquire(\n                websiteDataStore: store,\n                usageURL: url,\n                logger: nil)\n            webViews.append(lease.webView)\n            lease.release()\n        }\n\n        // All should be the same WebView instance\n        let firstWebView = webViews[0]\n        for (index, webView) in webViews.enumerated() {\n            #expect(\n                webView === firstWebView,\n                \"Fetch \\(index + 1) with real data store factory should reuse same WebView\")\n        }\n\n        #expect(cache.entryCount == 1, \"Should have single cached entry\")\n\n        cache.clearAllForTesting()\n        OpenAIDashboardWebsiteDataStore.clearCacheForTesting()\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/OpenAIWebAccountSwitchTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n@testable import CodexBar\n\n@MainActor\nstruct OpenAIWebAccountSwitchTests {\n    @Test\n    func `clears dashboard when codex email changes`() {\n        let settings = SettingsStore(\n            configStore: testConfigStore(suiteName: \"OpenAIWebAccountSwitchTests-clears\"),\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n        settings.refreshFrequency = .manual\n\n        let store = UsageStore(\n            fetcher: UsageFetcher(),\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            settings: settings)\n\n        store.handleOpenAIWebTargetEmailChangeIfNeeded(targetEmail: \"a@example.com\")\n        store.openAIDashboard = OpenAIDashboardSnapshot(\n            signedInEmail: \"a@example.com\",\n            codeReviewRemainingPercent: 100,\n            creditEvents: [],\n            dailyBreakdown: [],\n            usageBreakdown: [],\n            creditsPurchaseURL: nil,\n            updatedAt: Date())\n\n        store.handleOpenAIWebTargetEmailChangeIfNeeded(targetEmail: \"b@example.com\")\n        #expect(store.openAIDashboard == nil)\n        #expect(store.openAIDashboardRequiresLogin == true)\n        #expect(store.openAIDashboardCookieImportStatus?.contains(\"Codex account changed\") == true)\n    }\n\n    @Test\n    func `keeps dashboard when codex email stays same`() {\n        let settings = SettingsStore(\n            configStore: testConfigStore(suiteName: \"OpenAIWebAccountSwitchTests-keeps\"),\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n        settings.refreshFrequency = .manual\n\n        let store = UsageStore(\n            fetcher: UsageFetcher(),\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            settings: settings)\n\n        store.handleOpenAIWebTargetEmailChangeIfNeeded(targetEmail: \"a@example.com\")\n        let dash = OpenAIDashboardSnapshot(\n            signedInEmail: \"a@example.com\",\n            codeReviewRemainingPercent: 100,\n            creditEvents: [],\n            dailyBreakdown: [],\n            usageBreakdown: [],\n            creditsPurchaseURL: nil,\n            updatedAt: Date())\n        store.openAIDashboard = dash\n\n        store.handleOpenAIWebTargetEmailChangeIfNeeded(targetEmail: \"a@example.com\")\n        #expect(store.openAIDashboard == dash)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/OpenCodeUsageFetcherErrorTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n\n@Suite(.serialized)\nstruct OpenCodeUsageFetcherErrorTests {\n    @Test\n    func `extracts api error from uppercase HTML title`() async throws {\n        let registered = URLProtocol.registerClass(OpenCodeStubURLProtocol.self)\n        defer {\n            if registered {\n                URLProtocol.unregisterClass(OpenCodeStubURLProtocol.self)\n            }\n            OpenCodeStubURLProtocol.handler = nil\n        }\n\n        OpenCodeStubURLProtocol.handler = { request in\n            guard let url = request.url else { throw URLError(.badURL) }\n            let body = \"<html><head><TITLE>403 Forbidden</TITLE></head><body>denied</body></html>\"\n            return Self.makeResponse(url: url, body: body, statusCode: 500, contentType: \"text/html\")\n        }\n\n        do {\n            _ = try await OpenCodeUsageFetcher.fetchUsage(\n                cookieHeader: \"auth=test\",\n                timeout: 2,\n                workspaceIDOverride: \"wrk_TEST123\")\n            Issue.record(\"Expected OpenCodeUsageError.apiError\")\n        } catch let error as OpenCodeUsageError {\n            switch error {\n            case let .apiError(message):\n                #expect(message.contains(\"HTTP 500\"))\n                #expect(message.contains(\"403 Forbidden\"))\n            default:\n                Issue.record(\"Expected apiError, got: \\(error)\")\n            }\n        }\n    }\n\n    @Test\n    func `extracts api error from detail field`() async throws {\n        let registered = URLProtocol.registerClass(OpenCodeStubURLProtocol.self)\n        defer {\n            if registered {\n                URLProtocol.unregisterClass(OpenCodeStubURLProtocol.self)\n            }\n            OpenCodeStubURLProtocol.handler = nil\n        }\n\n        OpenCodeStubURLProtocol.handler = { request in\n            guard let url = request.url else { throw URLError(.badURL) }\n            let body = #\"{\"detail\":\"Workspace missing\"}\"#\n            return Self.makeResponse(url: url, body: body, statusCode: 500, contentType: \"application/json\")\n        }\n\n        do {\n            _ = try await OpenCodeUsageFetcher.fetchUsage(\n                cookieHeader: \"auth=test\",\n                timeout: 2,\n                workspaceIDOverride: \"wrk_TEST123\")\n            Issue.record(\"Expected OpenCodeUsageError.apiError\")\n        } catch let error as OpenCodeUsageError {\n            switch error {\n            case let .apiError(message):\n                #expect(message.contains(\"HTTP 500\"))\n                #expect(message.contains(\"Workspace missing\"))\n            default:\n                Issue.record(\"Expected apiError, got: \\(error)\")\n            }\n        }\n    }\n\n    @Test\n    func `subscription get null skips post and returns graceful error`() async throws {\n        let registered = URLProtocol.registerClass(OpenCodeStubURLProtocol.self)\n        defer {\n            if registered {\n                URLProtocol.unregisterClass(OpenCodeStubURLProtocol.self)\n            }\n            OpenCodeStubURLProtocol.handler = nil\n        }\n\n        var methods: [String] = []\n        var urls: [URL] = []\n        var queries: [String] = []\n        var contentTypes: [String] = []\n        OpenCodeStubURLProtocol.handler = { request in\n            guard let url = request.url else { throw URLError(.badURL) }\n            methods.append(request.httpMethod ?? \"GET\")\n            urls.append(url)\n            queries.append(url.query ?? \"\")\n            contentTypes.append(request.value(forHTTPHeaderField: \"Content-Type\") ?? \"\")\n\n            if request.httpMethod?.uppercased() == \"GET\" {\n                return Self.makeResponse(url: url, body: \"null\", statusCode: 200, contentType: \"application/json\")\n            }\n\n            let body = #\"{\"status\":500,\"unhandled\":true,\"message\":\"HTTPError\"}\"#\n            return Self.makeResponse(url: url, body: body, statusCode: 500, contentType: \"application/json\")\n        }\n\n        do {\n            _ = try await OpenCodeUsageFetcher.fetchUsage(\n                cookieHeader: \"auth=test\",\n                timeout: 2,\n                workspaceIDOverride: \"wrk_TEST123\")\n            Issue.record(\"Expected OpenCodeUsageError.apiError\")\n        } catch let error as OpenCodeUsageError {\n            switch error {\n            case let .apiError(message):\n                #expect(message.contains(\"No subscription usage data\"))\n                #expect(message.contains(\"wrk_TEST123\"))\n            default:\n                Issue.record(\"Expected apiError, got: \\(error)\")\n            }\n        }\n\n        #expect(methods == [\"GET\"])\n        #expect(queries[0].contains(\"id=\"))\n        #expect(queries[0].contains(\"wrk_TEST123\"))\n        #expect(urls[0].path == \"/_server\")\n        #expect(contentTypes[0].isEmpty)\n    }\n\n    @Test\n    func `subscription get payload does not fallback to post`() async throws {\n        let registered = URLProtocol.registerClass(OpenCodeStubURLProtocol.self)\n        defer {\n            if registered {\n                URLProtocol.unregisterClass(OpenCodeStubURLProtocol.self)\n            }\n            OpenCodeStubURLProtocol.handler = nil\n        }\n\n        var methods: [String] = []\n        OpenCodeStubURLProtocol.handler = { request in\n            guard let url = request.url else { throw URLError(.badURL) }\n            methods.append(request.httpMethod ?? \"GET\")\n\n            let body = \"\"\"\n            {\n              \"rollingUsage\": { \"usagePercent\": 17, \"resetInSec\": 600 },\n              \"weeklyUsage\": { \"usagePercent\": 75, \"resetInSec\": 7200 }\n            }\n            \"\"\"\n            return Self.makeResponse(url: url, body: body, statusCode: 200, contentType: \"application/json\")\n        }\n\n        let snapshot = try await OpenCodeUsageFetcher.fetchUsage(\n            cookieHeader: \"auth=test\",\n            timeout: 2,\n            workspaceIDOverride: \"wrk_TEST123\")\n\n        #expect(snapshot.rollingUsagePercent == 17)\n        #expect(snapshot.weeklyUsagePercent == 75)\n        #expect(methods == [\"GET\"])\n    }\n\n    @Test\n    func `subscription get missing fields falls back to post`() async throws {\n        let registered = URLProtocol.registerClass(OpenCodeStubURLProtocol.self)\n        defer {\n            if registered {\n                URLProtocol.unregisterClass(OpenCodeStubURLProtocol.self)\n            }\n            OpenCodeStubURLProtocol.handler = nil\n        }\n\n        var methods: [String] = []\n        OpenCodeStubURLProtocol.handler = { request in\n            guard let url = request.url else { throw URLError(.badURL) }\n            methods.append(request.httpMethod ?? \"GET\")\n\n            if request.httpMethod?.uppercased() == \"GET\" {\n                return Self.makeResponse(\n                    url: url,\n                    body: #\"{\"ok\":true}\"#,\n                    statusCode: 200,\n                    contentType: \"application/json\")\n            }\n\n            let body = \"\"\"\n            {\n              \"rollingUsage\": { \"usagePercent\": 22, \"resetInSec\": 300 },\n              \"weeklyUsage\": { \"usagePercent\": 44, \"resetInSec\": 3600 }\n            }\n            \"\"\"\n            return Self.makeResponse(\n                url: url,\n                body: body,\n                statusCode: 200,\n                contentType: \"application/json\")\n        }\n\n        let snapshot = try await OpenCodeUsageFetcher.fetchUsage(\n            cookieHeader: \"auth=test\",\n            timeout: 2,\n            workspaceIDOverride: \"wrk_TEST123\")\n\n        #expect(snapshot.rollingUsagePercent == 22)\n        #expect(snapshot.weeklyUsagePercent == 44)\n        #expect(methods == [\"GET\", \"POST\"])\n    }\n\n    private static func makeResponse(\n        url: URL,\n        body: String,\n        statusCode: Int,\n        contentType: String) -> (HTTPURLResponse, Data)\n    {\n        let response = HTTPURLResponse(\n            url: url,\n            statusCode: statusCode,\n            httpVersion: \"HTTP/1.1\",\n            headerFields: [\"Content-Type\": contentType])!\n        return (response, Data(body.utf8))\n    }\n}\n\nfinal class OpenCodeStubURLProtocol: URLProtocol {\n    nonisolated(unsafe) static var handler: ((URLRequest) throws -> (HTTPURLResponse, Data))?\n\n    override static func canInit(with request: URLRequest) -> Bool {\n        request.url?.host == \"opencode.ai\"\n    }\n\n    override static func canonicalRequest(for request: URLRequest) -> URLRequest {\n        request\n    }\n\n    override func startLoading() {\n        guard let handler = Self.handler else {\n            self.client?.urlProtocol(self, didFailWithError: URLError(.badServerResponse))\n            return\n        }\n        do {\n            let (response, data) = try handler(self.request)\n            self.client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)\n            self.client?.urlProtocol(self, didLoad: data)\n            self.client?.urlProtocolDidFinishLoading(self)\n        } catch {\n            self.client?.urlProtocol(self, didFailWithError: error)\n        }\n    }\n\n    override func stopLoading() {}\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/OpenCodeUsageParserTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\nstruct OpenCodeUsageParserTests {\n    @Test\n    func `parses workspace I ds`() {\n        let text = \";0x00000089;((self.$R=self.$R||{})[\\\"codexbar\\\"]=[],\" +\n            \"($R=>$R[0]=[$R[1]={id:\\\"wrk_01K6AR1ZET89H8NB691FQ2C2VB\\\",name:\\\"Default\\\",slug:null}])\" +\n            \"($R[\\\"codexbar\\\"]))\"\n        let ids = OpenCodeUsageFetcher.parseWorkspaceIDs(text: text)\n        #expect(ids == [\"wrk_01K6AR1ZET89H8NB691FQ2C2VB\"])\n    }\n\n    @Test\n    func `parses subscription usage`() throws {\n        let text = \"$R[16]($R[30],$R[41]={rollingUsage:$R[42]={status:\\\"ok\\\",resetInSec:5944,usagePercent:17},\" +\n            \"weeklyUsage:$R[43]={status:\\\"ok\\\",resetInSec:278201,usagePercent:75}});\"\n        let now = Date(timeIntervalSince1970: 0)\n        let snapshot = try OpenCodeUsageFetcher.parseSubscription(text: text, now: now)\n        #expect(snapshot.rollingUsagePercent == 17)\n        #expect(snapshot.weeklyUsagePercent == 75)\n        #expect(snapshot.rollingResetInSec == 5944)\n        #expect(snapshot.weeklyResetInSec == 278_201)\n    }\n\n    @Test\n    func `parses subscription from JSON with reset at`() throws {\n        let now = Date(timeIntervalSince1970: 1_700_000_000)\n        let resetAt = now.addingTimeInterval(3600)\n        let formatter = ISO8601DateFormatter()\n        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]\n        let payload: [String: Any] = [\n            \"usage\": [\n                \"rollingUsage\": [\n                    \"usagePercent\": 0.25,\n                    \"resetAt\": formatter.string(from: resetAt),\n                ],\n                \"weeklyUsage\": [\n                    \"usagePercent\": 75,\n                    \"resetInSec\": 7200,\n                ],\n            ],\n        ]\n        let data = try JSONSerialization.data(withJSONObject: payload)\n        let text = String(data: data, encoding: .utf8) ?? \"\"\n\n        let snapshot = try OpenCodeUsageFetcher.parseSubscription(text: text, now: now)\n\n        #expect(snapshot.rollingUsagePercent == 25)\n        #expect(snapshot.weeklyUsagePercent == 75)\n        #expect(snapshot.rollingResetInSec == 3600)\n        #expect(snapshot.weeklyResetInSec == 7200)\n    }\n\n    @Test\n    func `parses subscription from candidate windows`() throws {\n        let now = Date(timeIntervalSince1970: 1_700_000_000)\n        let payload: [String: Any] = [\n            \"windows\": [\n                \"primaryWindow\": [\n                    \"percent\": 0.1,\n                    \"resetInSec\": 300,\n                ],\n                \"secondaryWindow\": [\n                    \"percent\": 0.5,\n                    \"resetInSec\": 1200,\n                ],\n            ],\n        ]\n        let data = try JSONSerialization.data(withJSONObject: payload)\n        let text = String(data: data, encoding: .utf8) ?? \"\"\n\n        let snapshot = try OpenCodeUsageFetcher.parseSubscription(text: text, now: now)\n\n        #expect(snapshot.rollingUsagePercent == 10)\n        #expect(snapshot.weeklyUsagePercent == 50)\n        #expect(snapshot.rollingResetInSec == 300)\n        #expect(snapshot.weeklyResetInSec == 1200)\n    }\n\n    @Test\n    func `computes usage percent from totals`() throws {\n        let now = Date(timeIntervalSince1970: 1_700_000_000)\n        let payload: [String: Any] = [\n            \"rollingUsage\": [\n                \"used\": 25,\n                \"limit\": 100,\n                \"resetInSec\": 600,\n            ],\n            \"weeklyUsage\": [\n                \"used\": 50,\n                \"limit\": 200,\n                \"resetInSec\": 3600,\n            ],\n        ]\n        let data = try JSONSerialization.data(withJSONObject: payload)\n        let text = String(data: data, encoding: .utf8) ?? \"\"\n\n        let snapshot = try OpenCodeUsageFetcher.parseSubscription(text: text, now: now)\n\n        #expect(snapshot.rollingUsagePercent == 25)\n        #expect(snapshot.weeklyUsagePercent == 25)\n    }\n\n    @Test\n    func `parse subscription throws when fields missing`() {\n        let now = Date(timeIntervalSince1970: 1_700_000_000)\n        let text = \"{\\\"ok\\\":true}\"\n\n        #expect(throws: OpenCodeUsageError.self) {\n            _ = try OpenCodeUsageFetcher.parseSubscription(text: text, now: now)\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/OpenRouterUsageStatsTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\n@Suite(.serialized)\nstruct OpenRouterUsageStatsTests {\n    @Test\n    func `to usage snapshot uses key quota for primary window`() {\n        let snapshot = OpenRouterUsageSnapshot(\n            totalCredits: 50,\n            totalUsage: 45.3895596325,\n            balance: 4.6104403675,\n            usedPercent: 90.779119265,\n            keyLimit: 20,\n            keyUsage: 5,\n            rateLimit: nil,\n            updatedAt: Date(timeIntervalSince1970: 1_739_841_600))\n\n        let usage = snapshot.toUsageSnapshot()\n\n        #expect(usage.primary?.usedPercent == 25)\n        #expect(usage.primary?.resetsAt == nil)\n        #expect(usage.primary?.resetDescription == nil)\n        #expect(usage.openRouterUsage?.keyQuotaStatus == .available)\n    }\n\n    @Test\n    func `to usage snapshot without valid key limit omits primary window`() {\n        let snapshot = OpenRouterUsageSnapshot(\n            totalCredits: 50,\n            totalUsage: 45.3895596325,\n            balance: 4.6104403675,\n            usedPercent: 90.779119265,\n            keyLimit: nil,\n            keyUsage: nil,\n            rateLimit: nil,\n            updatedAt: Date(timeIntervalSince1970: 1_739_841_600))\n\n        let usage = snapshot.toUsageSnapshot()\n\n        #expect(usage.primary == nil)\n        #expect(usage.openRouterUsage?.keyQuotaStatus == .unavailable)\n    }\n\n    @Test\n    func `to usage snapshot when no limit configured omits primary and marks no limit`() {\n        let snapshot = OpenRouterUsageSnapshot(\n            totalCredits: 50,\n            totalUsage: 45.3895596325,\n            balance: 4.6104403675,\n            usedPercent: 90.779119265,\n            keyDataFetched: true,\n            keyLimit: nil,\n            keyUsage: nil,\n            rateLimit: nil,\n            updatedAt: Date(timeIntervalSince1970: 1_739_841_600))\n\n        let usage = snapshot.toUsageSnapshot()\n\n        #expect(usage.primary == nil)\n        #expect(usage.openRouterUsage?.keyQuotaStatus == .noLimitConfigured)\n    }\n\n    @Test\n    func `sanitizers redact sensitive token shapes`() {\n        let body = \"\"\"\n        {\"error\":\"bad token sk-or-v1-abc123\",\"token\":\"secret-token\",\"authorization\":\"Bearer sk-or-v1-xyz789\"}\n        \"\"\"\n\n        let summary = OpenRouterUsageFetcher._sanitizedResponseBodySummaryForTesting(body)\n        let debugBody = OpenRouterUsageFetcher._redactedDebugResponseBodyForTesting(body)\n\n        #expect(summary.contains(\"sk-or-v1-[REDACTED]\"))\n        #expect(summary.contains(\"\\\"token\\\":\\\"[REDACTED]\\\"\"))\n        #expect(!summary.contains(\"secret-token\"))\n        #expect(!summary.contains(\"sk-or-v1-abc123\"))\n\n        #expect(debugBody?.contains(\"sk-or-v1-[REDACTED]\") == true)\n        #expect(debugBody?.contains(\"\\\"token\\\":\\\"[REDACTED]\\\"\") == true)\n        #expect(debugBody?.contains(\"secret-token\") == false)\n        #expect(debugBody?.contains(\"sk-or-v1-xyz789\") == false)\n    }\n\n    @Test\n    func `non200 fetch throws generic HTTP error without body details`() async throws {\n        let registered = URLProtocol.registerClass(OpenRouterStubURLProtocol.self)\n        defer {\n            if registered {\n                URLProtocol.unregisterClass(OpenRouterStubURLProtocol.self)\n            }\n            OpenRouterStubURLProtocol.handler = nil\n        }\n\n        OpenRouterStubURLProtocol.handler = { request in\n            guard let url = request.url else { throw URLError(.badURL) }\n            let body = #\"{\"error\":\"invalid sk-or-v1-super-secret\",\"token\":\"dont-leak-me\"}\"#\n            return Self.makeResponse(url: url, body: body, statusCode: 401)\n        }\n\n        do {\n            _ = try await OpenRouterUsageFetcher.fetchUsage(\n                apiKey: \"sk-or-v1-test\",\n                environment: [\"OPENROUTER_API_URL\": \"https://openrouter.test/api/v1\"])\n            Issue.record(\"Expected OpenRouterUsageError.apiError\")\n        } catch let error as OpenRouterUsageError {\n            guard case let .apiError(message) = error else {\n                Issue.record(\"Expected apiError, got: \\(error)\")\n                return\n            }\n            #expect(message == \"HTTP 401\")\n            #expect(!message.contains(\"dont-leak-me\"))\n            #expect(!message.contains(\"sk-or-v1-super-secret\"))\n        }\n    }\n\n    @Test\n    func `fetch usage sets credits timeout and client headers`() async throws {\n        let registered = URLProtocol.registerClass(OpenRouterStubURLProtocol.self)\n        defer {\n            if registered {\n                URLProtocol.unregisterClass(OpenRouterStubURLProtocol.self)\n            }\n            OpenRouterStubURLProtocol.handler = nil\n        }\n\n        OpenRouterStubURLProtocol.handler = { request in\n            guard let url = request.url else { throw URLError(.badURL) }\n            switch url.path {\n            case \"/api/v1/credits\":\n                #expect(request.timeoutInterval == 15)\n                #expect(request.value(forHTTPHeaderField: \"HTTP-Referer\") == \"https://codexbar.example\")\n                #expect(request.value(forHTTPHeaderField: \"X-Title\") == \"CodexBar QA\")\n                let body = #\"{\"data\":{\"total_credits\":100,\"total_usage\":40}}\"#\n                return Self.makeResponse(url: url, body: body, statusCode: 200)\n            case \"/api/v1/key\":\n                let body = #\"{\"data\":{\"limit\":20,\"usage\":0.5,\"rate_limit\":{\"requests\":120,\"interval\":\"10s\"}}}\"#\n                return Self.makeResponse(url: url, body: body, statusCode: 200)\n            default:\n                return Self.makeResponse(url: url, body: \"{}\", statusCode: 404)\n            }\n        }\n\n        let usage = try await OpenRouterUsageFetcher.fetchUsage(\n            apiKey: \"sk-or-v1-test\",\n            environment: [\n                \"OPENROUTER_API_URL\": \"https://openrouter.test/api/v1\",\n                \"OPENROUTER_HTTP_REFERER\": \" https://codexbar.example \",\n                \"OPENROUTER_X_TITLE\": \"CodexBar QA\",\n            ])\n\n        #expect(usage.totalCredits == 100)\n        #expect(usage.totalUsage == 40)\n        #expect(usage.keyDataFetched)\n        #expect(usage.keyLimit == 20)\n        #expect(usage.keyUsage == 0.5)\n        #expect(usage.keyRemaining == 19.5)\n        #expect(usage.keyUsedPercent == 2.5)\n        #expect(usage.keyQuotaStatus == .available)\n    }\n\n    @Test\n    func `fetch usage when key endpoint fails marks quota unavailable`() async throws {\n        let registered = URLProtocol.registerClass(OpenRouterStubURLProtocol.self)\n        defer {\n            if registered {\n                URLProtocol.unregisterClass(OpenRouterStubURLProtocol.self)\n            }\n            OpenRouterStubURLProtocol.handler = nil\n        }\n\n        OpenRouterStubURLProtocol.handler = { request in\n            guard let url = request.url else { throw URLError(.badURL) }\n            switch url.path {\n            case \"/api/v1/credits\":\n                let body = #\"{\"data\":{\"total_credits\":100,\"total_usage\":40}}\"#\n                return Self.makeResponse(url: url, body: body, statusCode: 200)\n            case \"/api/v1/key\":\n                return Self.makeResponse(url: url, body: \"{}\", statusCode: 500)\n            default:\n                return Self.makeResponse(url: url, body: \"{}\", statusCode: 404)\n            }\n        }\n\n        let usage = try await OpenRouterUsageFetcher.fetchUsage(\n            apiKey: \"sk-or-v1-test\",\n            environment: [\"OPENROUTER_API_URL\": \"https://openrouter.test/api/v1\"])\n\n        #expect(!usage.keyDataFetched)\n        #expect(usage.keyQuotaStatus == .unavailable)\n    }\n\n    @Test\n    func `usage snapshot round trip persists open router usage metadata`() throws {\n        let openRouter = OpenRouterUsageSnapshot(\n            totalCredits: 50,\n            totalUsage: 45.3895596325,\n            balance: 4.6104403675,\n            usedPercent: 90.779119265,\n            keyDataFetched: true,\n            keyLimit: nil,\n            keyUsage: nil,\n            rateLimit: nil,\n            updatedAt: Date(timeIntervalSince1970: 1_739_841_600))\n        let snapshot = openRouter.toUsageSnapshot()\n\n        let encoder = JSONEncoder()\n        let data = try encoder.encode(snapshot)\n        let decoded = try JSONDecoder().decode(UsageSnapshot.self, from: data)\n\n        #expect(decoded.openRouterUsage?.keyDataFetched == true)\n        #expect(decoded.openRouterUsage?.keyQuotaStatus == .noLimitConfigured)\n    }\n\n    private static func makeResponse(\n        url: URL,\n        body: String,\n        statusCode: Int = 200) -> (HTTPURLResponse, Data)\n    {\n        let response = HTTPURLResponse(\n            url: url,\n            statusCode: statusCode,\n            httpVersion: \"HTTP/1.1\",\n            headerFields: [\"Content-Type\": \"application/json\"])!\n        return (response, Data(body.utf8))\n    }\n}\n\nfinal class OpenRouterStubURLProtocol: URLProtocol {\n    nonisolated(unsafe) static var handler: ((URLRequest) throws -> (HTTPURLResponse, Data))?\n\n    override static func canInit(with request: URLRequest) -> Bool {\n        request.url?.host == \"openrouter.test\"\n    }\n\n    override static func canonicalRequest(for request: URLRequest) -> URLRequest {\n        request\n    }\n\n    override func startLoading() {\n        guard let handler = Self.handler else {\n            self.client?.urlProtocol(self, didFailWithError: URLError(.badServerResponse))\n            return\n        }\n        do {\n            let (response, data) = try handler(self.request)\n            self.client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)\n            self.client?.urlProtocol(self, didLoad: data)\n            self.client?.urlProtocolDidFinishLoading(self)\n        } catch {\n            self.client?.urlProtocol(self, didFailWithError: error)\n        }\n    }\n\n    override func stopLoading() {}\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/PathBuilderTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n@testable import CodexBar\n\nstruct PathBuilderTests {\n    @Test\n    func `merges login shell path when available`() {\n        let seeded = PathBuilder.effectivePATH(\n            purposes: [.rpc],\n            env: [\"PATH\": \"/custom/bin:/usr/bin\"],\n            loginPATH: [\"/login/bin\", \"/login/alt\"])\n        #expect(seeded == \"/login/bin:/login/alt:/custom/bin:/usr/bin\")\n    }\n\n    @Test\n    func `falls back to existing path when no login path`() {\n        let seeded = PathBuilder.effectivePATH(\n            purposes: [.tty],\n            env: [\"PATH\": \"/custom/bin:/usr/bin\"],\n            loginPATH: nil)\n        #expect(seeded == \"/custom/bin:/usr/bin\")\n    }\n\n    @Test\n    func `uses fallback when no path available`() {\n        let seeded = PathBuilder.effectivePATH(\n            purposes: [.tty],\n            env: [:],\n            loginPATH: nil)\n        #expect(seeded == \"/usr/bin:/bin:/usr/sbin:/sbin\")\n    }\n\n    @Test\n    func `debug snapshot async matches sync`() async {\n        let env = [\n            \"CODEX_CLI_PATH\": \"/usr/bin/true\",\n            \"CLAUDE_CLI_PATH\": \"/usr/bin/true\",\n            \"GEMINI_CLI_PATH\": \"/usr/bin/true\",\n            \"PATH\": \"/usr/bin:/bin\",\n        ]\n        let sync = PathBuilder.debugSnapshot(purposes: [.rpc], env: env, home: \"/tmp\")\n        let async = await PathBuilder.debugSnapshotAsync(purposes: [.rpc], env: env, home: \"/tmp\")\n        #expect(async == sync)\n    }\n\n    @Test\n    func `resolves codex from env override`() {\n        let overridePath = \"/custom/bin/codex\"\n        let fm = MockFileManager(executables: [overridePath])\n\n        let resolved = BinaryLocator.resolveCodexBinary(\n            env: [\"CODEX_CLI_PATH\": overridePath],\n            loginPATH: nil,\n            fileManager: fm,\n            home: \"/home/test\")\n        #expect(resolved == overridePath)\n    }\n\n    @Test\n    func `resolves codex from login path`() {\n        let fm = MockFileManager(executables: [\"/login/bin/codex\"])\n        let resolved = BinaryLocator.resolveCodexBinary(\n            env: [\"PATH\": \"/env/bin\"],\n            loginPATH: [\"/login/bin\"],\n            fileManager: fm,\n            home: \"/home/test\")\n        #expect(resolved == \"/login/bin/codex\")\n    }\n\n    @Test\n    func `resolves codex from env path`() {\n        let fm = MockFileManager(executables: [\"/env/bin/codex\"])\n        let resolved = BinaryLocator.resolveCodexBinary(\n            env: [\"PATH\": \"/env/bin:/usr/bin\"],\n            loginPATH: nil,\n            fileManager: fm,\n            home: \"/home/test\")\n        #expect(resolved == \"/env/bin/codex\")\n    }\n\n    @Test\n    func `resolves codex from interactive shell`() {\n        let fm = MockFileManager(executables: [\"/shell/bin/codex\"])\n        let commandV: (String, String?, TimeInterval, FileManager) -> String? = { tool, shell, timeout, fileManager in\n            #expect(tool == \"codex\")\n            #expect(shell == \"/bin/zsh\")\n            #expect(timeout == 2.0)\n            _ = fileManager\n            return \"/shell/bin/codex\"\n        }\n\n        let resolved = BinaryLocator.resolveCodexBinary(\n            env: [\"SHELL\": \"/bin/zsh\"],\n            loginPATH: nil,\n            commandV: commandV,\n            fileManager: fm,\n            home: \"/home/test\")\n        #expect(resolved == \"/shell/bin/codex\")\n    }\n\n    @Test\n    func `resolves claude from interactive shell`() {\n        let fm = MockFileManager(executables: [\"/shell/bin/claude\"])\n        let commandV: (String, String?, TimeInterval, FileManager) -> String? = { tool, shell, timeout, fileManager in\n            #expect(tool == \"claude\")\n            #expect(shell == \"/bin/zsh\")\n            #expect(timeout == 2.0)\n            _ = fileManager\n            return \"/shell/bin/claude\"\n        }\n\n        let resolved = BinaryLocator.resolveClaudeBinary(\n            env: [\"SHELL\": \"/bin/zsh\"],\n            loginPATH: nil,\n            commandV: commandV,\n            fileManager: fm,\n            home: \"/home/test\")\n        #expect(resolved == \"/shell/bin/claude\")\n    }\n\n    @Test\n    func `resolves gemini from interactive shell`() {\n        let fm = MockFileManager(executables: [\"/shell/bin/gemini\"])\n        let commandV: (String, String?, TimeInterval, FileManager) -> String? = { tool, shell, timeout, fileManager in\n            #expect(tool == \"gemini\")\n            #expect(shell == \"/bin/zsh\")\n            #expect(timeout == 2.0)\n            _ = fileManager\n            return \"/shell/bin/gemini\"\n        }\n\n        let resolved = BinaryLocator.resolveGeminiBinary(\n            env: [\"SHELL\": \"/bin/zsh\"],\n            loginPATH: nil,\n            commandV: commandV,\n            fileManager: fm,\n            home: \"/home/test\")\n        #expect(resolved == \"/shell/bin/gemini\")\n    }\n\n    @Test\n    func `resolves claude from login path`() {\n        let fm = MockFileManager(executables: [\"/login/bin/claude\"])\n        let resolved = BinaryLocator.resolveClaudeBinary(\n            env: [\"PATH\": \"/env/bin\"],\n            loginPATH: [\"/login/bin\"],\n            fileManager: fm,\n            home: \"/home/test\")\n        #expect(resolved == \"/login/bin/claude\")\n    }\n\n    @Test\n    func `resolves claude from alias when other lookups fail`() {\n        let aliasPath = \"/home/test/.claude/local/bin/claude\"\n        let fm = MockFileManager(executables: [aliasPath])\n        var aliasCalled = false\n        let aliasResolver: (String, String?, TimeInterval, FileManager, String)\n            -> String? = { tool, shell, timeout, _, home in\n                aliasCalled = true\n                #expect(tool == \"claude\")\n                #expect(shell == \"/bin/zsh\")\n                #expect(timeout == 2.0)\n                #expect(home == \"/home/test\")\n                return aliasPath\n            }\n        let commandV: (String, String?, TimeInterval, FileManager) -> String? = { _, _, _, _ in\n            nil\n        }\n\n        let resolved = BinaryLocator.resolveClaudeBinary(\n            env: [\"SHELL\": \"/bin/zsh\"],\n            loginPATH: nil,\n            commandV: commandV,\n            aliasResolver: aliasResolver,\n            fileManager: fm,\n            home: \"/home/test\")\n\n        #expect(aliasCalled)\n        #expect(resolved == aliasPath)\n    }\n\n    @Test\n    func `resolves codex from alias when other lookups fail`() {\n        let aliasPath = \"/home/test/.codex/bin/codex\"\n        let fm = MockFileManager(executables: [aliasPath])\n        var aliasCalled = false\n        let aliasResolver: (String, String?, TimeInterval, FileManager, String)\n            -> String? = { tool, shell, timeout, _, home in\n                aliasCalled = true\n                #expect(tool == \"codex\")\n                #expect(shell == \"/bin/zsh\")\n                #expect(timeout == 2.0)\n                #expect(home == \"/home/test\")\n                return aliasPath\n            }\n        let commandV: (String, String?, TimeInterval, FileManager) -> String? = { _, _, _, _ in\n            nil\n        }\n\n        let resolved = BinaryLocator.resolveCodexBinary(\n            env: [\"SHELL\": \"/bin/zsh\"],\n            loginPATH: nil,\n            commandV: commandV,\n            aliasResolver: aliasResolver,\n            fileManager: fm,\n            home: \"/home/test\")\n\n        #expect(aliasCalled)\n        #expect(resolved == aliasPath)\n    }\n\n    @Test\n    func `skips alias when command V resolves`() {\n        let path = \"/shell/bin/claude\"\n        let fm = MockFileManager(executables: [path])\n        var aliasCalled = false\n        let aliasResolver: (String, String?, TimeInterval, FileManager, String) -> String? = { _, _, _, _, _ in\n            aliasCalled = true\n            return \"/alias/claude\"\n        }\n        let commandV: (String, String?, TimeInterval, FileManager) -> String? = { _, _, _, _ in\n            path\n        }\n\n        let resolved = BinaryLocator.resolveClaudeBinary(\n            env: [\"SHELL\": \"/bin/zsh\"],\n            loginPATH: nil,\n            commandV: commandV,\n            aliasResolver: aliasResolver,\n            fileManager: fm,\n            home: \"/home/test\")\n\n        #expect(!aliasCalled)\n        #expect(resolved == path)\n    }\n}\n\nprivate final class MockFileManager: FileManager {\n    private let executables: Set<String>\n\n    init(executables: Set<String>) {\n        self.executables = executables\n    }\n\n    override func isExecutableFile(atPath path: String) -> Bool {\n        self.executables.contains(path)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/PreferencesPaneSmokeTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n@testable import CodexBar\n\n@MainActor\nstruct PreferencesPaneSmokeTests {\n    @Test\n    func `builds preference panes with default settings`() {\n        let settings = Self.makeSettingsStore(suite: \"PreferencesPaneSmokeTests-default\")\n        let store = Self.makeUsageStore(settings: settings)\n\n        _ = GeneralPane(settings: settings, store: store).body\n        _ = DisplayPane(settings: settings, store: store).body\n        _ = AdvancedPane(settings: settings).body\n        _ = ProvidersPane(settings: settings, store: store).body\n        _ = DebugPane(settings: settings, store: store).body\n        _ = AboutPane(updater: DisabledUpdaterController()).body\n\n        settings.debugDisableKeychainAccess = false\n    }\n\n    @Test\n    func `builds preference panes with toggled settings`() {\n        let settings = Self.makeSettingsStore(suite: \"PreferencesPaneSmokeTests-toggled\")\n        settings.menuBarShowsBrandIconWithPercent = true\n        settings.menuBarShowsHighestUsage = true\n        settings.showAllTokenAccountsInMenu = true\n        settings.hidePersonalInfo = true\n        settings.resetTimesShowAbsolute = true\n        settings.debugDisableKeychainAccess = true\n        settings.claudeOAuthKeychainPromptMode = .always\n        settings.refreshFrequency = .manual\n\n        let store = Self.makeUsageStore(settings: settings)\n        store._setErrorForTesting(\"Example error\", provider: .codex)\n\n        _ = GeneralPane(settings: settings, store: store).body\n        _ = DisplayPane(settings: settings, store: store).body\n        _ = AdvancedPane(settings: settings).body\n        _ = ProvidersPane(settings: settings, store: store).body\n        _ = DebugPane(settings: settings, store: store).body\n        _ = AboutPane(updater: DisabledUpdaterController()).body\n    }\n\n    private static func makeSettingsStore(suite: String) -> SettingsStore {\n        let defaults = UserDefaults(suiteName: suite)!\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n\n        return SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore(),\n            codexCookieStore: InMemoryCookieHeaderStore(),\n            claudeCookieStore: InMemoryCookieHeaderStore(),\n            cursorCookieStore: InMemoryCookieHeaderStore(),\n            opencodeCookieStore: InMemoryCookieHeaderStore(),\n            factoryCookieStore: InMemoryCookieHeaderStore(),\n            minimaxCookieStore: InMemoryMiniMaxCookieStore(),\n            minimaxAPITokenStore: InMemoryMiniMaxAPITokenStore(),\n            kimiTokenStore: InMemoryKimiTokenStore(),\n            kimiK2TokenStore: InMemoryKimiK2TokenStore(),\n            augmentCookieStore: InMemoryCookieHeaderStore(),\n            ampCookieStore: InMemoryCookieHeaderStore(),\n            copilotTokenStore: InMemoryCopilotTokenStore(),\n            tokenAccountStore: InMemoryTokenAccountStore())\n    }\n\n    private static func makeUsageStore(settings: SettingsStore) -> UsageStore {\n        UsageStore(\n            fetcher: UsageFetcher(environment: [:]),\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            settings: settings)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/ProviderCandidateRetryRunnerTests.swift",
    "content": "import Testing\n@testable import CodexBarCore\n\nstruct ProviderCandidateRetryRunnerTests {\n    private enum TestError: Error, Equatable {\n        case retryable(Int)\n        case nonRetryable(Int)\n    }\n\n    @Test\n    func `retries then succeeds`() async throws {\n        let candidates = [1, 2, 3]\n        var attempted: [Int] = []\n        var retried: [Int] = []\n\n        let output = try await ProviderCandidateRetryRunner.run(\n            candidates,\n            shouldRetry: { error in\n                if case TestError.retryable = error {\n                    return true\n                }\n                return false\n            },\n            onRetry: { candidate, _ in\n                retried.append(candidate)\n            },\n            attempt: { candidate in\n                attempted.append(candidate)\n                guard candidate == 3 else {\n                    throw TestError.retryable(candidate)\n                }\n                return candidate * 10\n            })\n\n        #expect(output == 30)\n        #expect(attempted == [1, 2, 3])\n        #expect(retried == [1, 2])\n    }\n\n    @Test\n    func `non retryable fails immediately`() async {\n        let candidates = [1, 2, 3]\n        var attempted: [Int] = []\n        var retried: [Int] = []\n\n        do {\n            _ = try await ProviderCandidateRetryRunner.run(\n                candidates,\n                shouldRetry: { error in\n                    if case TestError.retryable = error {\n                        return true\n                    }\n                    return false\n                },\n                onRetry: { candidate, _ in\n                    retried.append(candidate)\n                },\n                attempt: { candidate in\n                    attempted.append(candidate)\n                    throw TestError.nonRetryable(candidate)\n                })\n            Issue.record(\"Expected TestError.nonRetryable\")\n        } catch let error as TestError {\n            #expect(error == .nonRetryable(1))\n            #expect(attempted == [1])\n            #expect(retried.isEmpty)\n        } catch {\n            Issue.record(\"Expected TestError.nonRetryable(1), got \\(error)\")\n        }\n    }\n\n    @Test\n    func `exhausted retryable throws last error`() async {\n        let candidates = [1, 2]\n        var attempted: [Int] = []\n        var retried: [Int] = []\n\n        do {\n            _ = try await ProviderCandidateRetryRunner.run(\n                candidates,\n                shouldRetry: { error in\n                    if case TestError.retryable = error {\n                        return true\n                    }\n                    return false\n                },\n                onRetry: { candidate, _ in\n                    retried.append(candidate)\n                },\n                attempt: { candidate in\n                    attempted.append(candidate)\n                    throw TestError.retryable(candidate)\n                })\n            Issue.record(\"Expected TestError.retryable\")\n        } catch let error as TestError {\n            #expect(error == .retryable(2))\n            #expect(attempted == [1, 2])\n            #expect(retried == [1])\n        } catch {\n            Issue.record(\"Expected TestError.retryable(2), got \\(error)\")\n        }\n    }\n\n    @Test\n    func `empty candidates throws no candidates`() async {\n        do {\n            let candidates: [Int] = []\n            _ = try await ProviderCandidateRetryRunner.run(\n                candidates,\n                shouldRetry: { _ in true },\n                attempt: { _ in 1 })\n            Issue.record(\"Expected ProviderCandidateRetryRunnerError.noCandidates\")\n        } catch ProviderCandidateRetryRunnerError.noCandidates {\n            // expected\n        } catch {\n            Issue.record(\"Expected ProviderCandidateRetryRunnerError.noCandidates, got \\(error)\")\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/ProviderConfigEnvironmentTests.swift",
    "content": "import CodexBarCore\nimport Testing\n\nstruct ProviderConfigEnvironmentTests {\n    @Test\n    func `applies API key override for zai`() {\n        let config = ProviderConfig(id: .zai, apiKey: \"z-token\")\n        let env = ProviderConfigEnvironment.applyAPIKeyOverride(\n            base: [:],\n            provider: .zai,\n            config: config)\n\n        #expect(env[ZaiSettingsReader.apiTokenKey] == \"z-token\")\n    }\n\n    @Test\n    func `applies API key override for warp`() {\n        let config = ProviderConfig(id: .warp, apiKey: \"w-token\")\n        let env = ProviderConfigEnvironment.applyAPIKeyOverride(\n            base: [:],\n            provider: .warp,\n            config: config)\n\n        let key = WarpSettingsReader.apiKeyEnvironmentKeys.first\n        #expect(key != nil)\n        guard let key else { return }\n\n        #expect(env[key] == \"w-token\")\n    }\n\n    @Test\n    func `applies API key override for open router`() {\n        let config = ProviderConfig(id: .openrouter, apiKey: \"or-token\")\n        let env = ProviderConfigEnvironment.applyAPIKeyOverride(\n            base: [:],\n            provider: .openrouter,\n            config: config)\n\n        #expect(env[OpenRouterSettingsReader.envKey] == \"or-token\")\n    }\n\n    @Test\n    func `applies API key override for kilo`() {\n        let config = ProviderConfig(id: .kilo, apiKey: \"kilo-token\")\n        let env = ProviderConfigEnvironment.applyAPIKeyOverride(\n            base: [:],\n            provider: .kilo,\n            config: config)\n\n        #expect(env[KiloSettingsReader.apiTokenKey] == \"kilo-token\")\n        #expect(ProviderTokenResolver.kiloToken(environment: env, authFileURL: nil) == \"kilo-token\")\n    }\n\n    @Test\n    func `open router config override wins over environment token`() {\n        let config = ProviderConfig(id: .openrouter, apiKey: \"config-token\")\n        let env = ProviderConfigEnvironment.applyAPIKeyOverride(\n            base: [OpenRouterSettingsReader.envKey: \"env-token\"],\n            provider: .openrouter,\n            config: config)\n\n        #expect(env[OpenRouterSettingsReader.envKey] == \"config-token\")\n        #expect(ProviderTokenResolver.openRouterToken(environment: env) == \"config-token\")\n    }\n\n    @Test\n    func `leaves environment when API key missing`() {\n        let config = ProviderConfig(id: .zai, apiKey: nil)\n        let env = ProviderConfigEnvironment.applyAPIKeyOverride(\n            base: [ZaiSettingsReader.apiTokenKey: \"existing\"],\n            provider: .zai,\n            config: config)\n\n        #expect(env[ZaiSettingsReader.apiTokenKey] == \"existing\")\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/ProviderIconResourcesTests.swift",
    "content": "import AppKit\nimport Foundation\nimport Testing\n\n@MainActor\nstruct ProviderIconResourcesTests {\n    @Test\n    func `provider icon SV gs exist`() throws {\n        let root = try Self.repoRoot()\n        let resources = root.appending(path: \"Sources/CodexBar/Resources\", directoryHint: .isDirectory)\n\n        let slugs = [\n            \"codex\",\n            \"claude\",\n            \"zai\",\n            \"minimax\",\n            \"cursor\",\n            \"opencode\",\n            \"alibaba\",\n            \"gemini\",\n            \"antigravity\",\n            \"factory\",\n            \"copilot\",\n        ]\n        for slug in slugs {\n            let url = resources.appending(path: \"ProviderIcon-\\(slug).svg\")\n            #expect(\n                FileManager.default.fileExists(atPath: url.path(percentEncoded: false)),\n                \"Missing SVG for \\(slug)\")\n\n            let image = NSImage(contentsOf: url)\n            #expect(image != nil, \"Could not load SVG as NSImage for \\(slug)\")\n        }\n    }\n\n    private static func repoRoot() throws -> URL {\n        var dir = URL(filePath: #filePath).deletingLastPathComponent()\n        for _ in 0..<12 {\n            let candidate = dir.appending(path: \"Package.swift\")\n            if FileManager.default.fileExists(atPath: candidate.path(percentEncoded: false)) {\n                return dir\n            }\n            dir.deleteLastPathComponent()\n        }\n        throw NSError(domain: \"ProviderIconResourcesTests\", code: 2, userInfo: [\n            NSLocalizedDescriptionKey: \"Could not locate repo root (Package.swift) from \\(#filePath)\",\n        ])\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/ProviderMetadataStatusLinkTests.swift",
    "content": "import Testing\n@testable import CodexBarCore\n\nstruct ProviderMetadataStatusLinkTests {\n    @Test\n    func `workspace status link matches product ID`() {\n        for (provider, meta) in ProviderDefaults.metadata {\n            guard let productID = meta.statusWorkspaceProductID else { continue }\n            let expected = \"https://www.google.com/appsstatus/dashboard/products/\\(productID)/history\"\n            #expect(\n                meta.statusLinkURL == expected,\n                \"Expected \\(provider.rawValue) statusLinkURL to be \\(expected)\")\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/ProviderRegistryTests.swift",
    "content": "import CodexBarCore\nimport Testing\n\nstruct ProviderRegistryTests {\n    @Test\n    func `descriptor registry is complete and deterministic`() {\n        let descriptors = ProviderDescriptorRegistry.all\n        let ids = descriptors.map(\\.id)\n\n        #expect(!descriptors.isEmpty, \"ProviderDescriptorRegistry must not be empty.\")\n        #expect(Set(ids).count == ids.count, \"ProviderDescriptorRegistry contains duplicate IDs.\")\n\n        let missing = Set(UsageProvider.allCases).subtracting(ids)\n        #expect(missing.isEmpty, \"Missing descriptors for providers: \\(missing).\")\n\n        let secondPass = ProviderDescriptorRegistry.all.map(\\.id)\n        #expect(ids == secondPass, \"ProviderDescriptorRegistry order changed between reads.\")\n    }\n\n    @Test\n    func `minimax sorts after zai in registry`() {\n        let ids = ProviderDescriptorRegistry.all.map(\\.id)\n        guard let zaiIndex = ids.firstIndex(of: .zai),\n              let minimaxIndex = ids.firstIndex(of: .minimax)\n        else {\n            Issue.record(\"Missing z.ai or MiniMax provider in registry order.\")\n            return\n        }\n\n        #expect(zaiIndex < minimaxIndex)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/ProviderSettingsDescriptorTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport SwiftUI\nimport Testing\n@testable import CodexBar\n\n@MainActor\nstruct ProviderSettingsDescriptorTests {\n    @Test\n    func `toggle I ds are unique across providers`() throws {\n        let suite = \"ProviderSettingsDescriptorTests-unique\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n        let settings = SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n        let store = UsageStore(\n            fetcher: UsageFetcher(environment: [:]),\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            settings: settings)\n\n        var statusByID: [String: String] = [:]\n        var lastRunAtByID: [String: Date] = [:]\n        var seenToggleIDs: Set<String> = []\n        var seenActionIDs: Set<String> = []\n        var seenPickerIDs: Set<String> = []\n\n        for provider in UsageProvider.allCases {\n            let context = ProviderSettingsContext(\n                provider: provider,\n                settings: settings,\n                store: store,\n                boolBinding: { keyPath in\n                    Binding(\n                        get: { settings[keyPath: keyPath] },\n                        set: { settings[keyPath: keyPath] = $0 })\n                },\n                stringBinding: { keyPath in\n                    Binding(\n                        get: { settings[keyPath: keyPath] },\n                        set: { settings[keyPath: keyPath] = $0 })\n                },\n                statusText: { id in statusByID[id] },\n                setStatusText: { id, text in\n                    if let text {\n                        statusByID[id] = text\n                    } else {\n                        statusByID.removeValue(forKey: id)\n                    }\n                },\n                lastAppActiveRunAt: { id in lastRunAtByID[id] },\n                setLastAppActiveRunAt: { id, date in\n                    if let date {\n                        lastRunAtByID[id] = date\n                    } else {\n                        lastRunAtByID.removeValue(forKey: id)\n                    }\n                },\n                requestConfirmation: { _ in })\n\n            let impl = try #require(ProviderCatalog.implementation(for: provider))\n            let toggles = impl.settingsToggles(context: context)\n            for toggle in toggles {\n                #expect(!seenToggleIDs.contains(toggle.id))\n                seenToggleIDs.insert(toggle.id)\n\n                for action in toggle.actions {\n                    #expect(!seenActionIDs.contains(action.id))\n                    seenActionIDs.insert(action.id)\n                }\n            }\n\n            let pickers = impl.settingsPickers(context: context)\n            for picker in pickers {\n                #expect(!seenPickerIDs.contains(picker.id))\n                seenPickerIDs.insert(picker.id)\n            }\n        }\n    }\n\n    @Test\n    func `codex exposes usage and cookie pickers`() throws {\n        let suite = \"ProviderSettingsDescriptorTests-codex\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n        let settings = SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n        let store = UsageStore(\n            fetcher: UsageFetcher(environment: [:]),\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            settings: settings)\n\n        let context = ProviderSettingsContext(\n            provider: .codex,\n            settings: settings,\n            store: store,\n            boolBinding: { keyPath in\n                Binding(\n                    get: { settings[keyPath: keyPath] },\n                    set: { settings[keyPath: keyPath] = $0 })\n            },\n            stringBinding: { keyPath in\n                Binding(\n                    get: { settings[keyPath: keyPath] },\n                    set: { settings[keyPath: keyPath] = $0 })\n            },\n            statusText: { _ in nil },\n            setStatusText: { _, _ in },\n            lastAppActiveRunAt: { _ in nil },\n            setLastAppActiveRunAt: { _, _ in },\n            requestConfirmation: { _ in })\n\n        let pickers = CodexProviderImplementation().settingsPickers(context: context)\n        let toggles = CodexProviderImplementation().settingsToggles(context: context)\n        #expect(pickers.contains(where: { $0.id == \"codex-usage-source\" }))\n        #expect(pickers.contains(where: { $0.id == \"codex-cookie-source\" }))\n        #expect(toggles.contains(where: { $0.id == \"codex-historical-tracking\" }))\n    }\n\n    @Test\n    func `claude exposes usage and cookie pickers`() throws {\n        let suite = \"ProviderSettingsDescriptorTests-claude\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n        let settings = SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n        settings.debugDisableKeychainAccess = false\n        let store = UsageStore(\n            fetcher: UsageFetcher(environment: [:]),\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            settings: settings)\n\n        let context = ProviderSettingsContext(\n            provider: .claude,\n            settings: settings,\n            store: store,\n            boolBinding: { keyPath in\n                Binding(\n                    get: { settings[keyPath: keyPath] },\n                    set: { settings[keyPath: keyPath] = $0 })\n            },\n            stringBinding: { keyPath in\n                Binding(\n                    get: { settings[keyPath: keyPath] },\n                    set: { settings[keyPath: keyPath] = $0 })\n            },\n            statusText: { _ in nil },\n            setStatusText: { _, _ in },\n            lastAppActiveRunAt: { _ in nil },\n            setLastAppActiveRunAt: { _, _ in },\n            requestConfirmation: { _ in })\n        let pickers = ClaudeProviderImplementation().settingsPickers(context: context)\n        #expect(pickers.contains(where: { $0.id == \"claude-usage-source\" }))\n        #expect(pickers.contains(where: { $0.id == \"claude-cookie-source\" }))\n        let keychainPicker = try #require(pickers.first(where: { $0.id == \"claude-keychain-prompt-policy\" }))\n        let optionIDs = Set(keychainPicker.options.map(\\.id))\n        #expect(optionIDs.contains(ClaudeOAuthKeychainPromptMode.never.rawValue))\n        #expect(optionIDs.contains(ClaudeOAuthKeychainPromptMode.onlyOnUserAction.rawValue))\n        #expect(optionIDs.contains(ClaudeOAuthKeychainPromptMode.always.rawValue))\n        #expect(keychainPicker.isEnabled?() ?? true)\n    }\n\n    @Test\n    func `claude prompt policy picker hidden when experimental reader selected`() throws {\n        let suite = \"ProviderSettingsDescriptorTests-claude-prompt-hidden-experimental\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n        let settings = SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n        settings.debugDisableKeychainAccess = false\n        settings.claudeOAuthKeychainReadStrategy = .securityCLIExperimental\n\n        let store = UsageStore(\n            fetcher: UsageFetcher(environment: [:]),\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            settings: settings)\n\n        let context = ProviderSettingsContext(\n            provider: .claude,\n            settings: settings,\n            store: store,\n            boolBinding: { keyPath in\n                Binding(\n                    get: { settings[keyPath: keyPath] },\n                    set: { settings[keyPath: keyPath] = $0 })\n            },\n            stringBinding: { keyPath in\n                Binding(\n                    get: { settings[keyPath: keyPath] },\n                    set: { settings[keyPath: keyPath] = $0 })\n            },\n            statusText: { _ in nil },\n            setStatusText: { _, _ in },\n            lastAppActiveRunAt: { _ in nil },\n            setLastAppActiveRunAt: { _, _ in },\n            requestConfirmation: { _ in })\n\n        let pickers = ClaudeProviderImplementation().settingsPickers(context: context)\n        let keychainPicker = try #require(pickers.first(where: { $0.id == \"claude-keychain-prompt-policy\" }))\n        #expect(keychainPicker.isVisible?() == false)\n    }\n\n    @Test\n    func `claude keychain prompt policy picker disabled when global keychain disabled`() throws {\n        let suite = \"ProviderSettingsDescriptorTests-claude-keychain-disabled\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n        let settings = SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n        settings.debugDisableKeychainAccess = true\n        let store = UsageStore(\n            fetcher: UsageFetcher(environment: [:]),\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            settings: settings)\n\n        let context = ProviderSettingsContext(\n            provider: .claude,\n            settings: settings,\n            store: store,\n            boolBinding: { keyPath in\n                Binding(\n                    get: { settings[keyPath: keyPath] },\n                    set: { settings[keyPath: keyPath] = $0 })\n            },\n            stringBinding: { keyPath in\n                Binding(\n                    get: { settings[keyPath: keyPath] },\n                    set: { settings[keyPath: keyPath] = $0 })\n            },\n            statusText: { _ in nil },\n            setStatusText: { _, _ in },\n            lastAppActiveRunAt: { _ in nil },\n            setLastAppActiveRunAt: { _, _ in },\n            requestConfirmation: { _ in })\n\n        let pickers = ClaudeProviderImplementation().settingsPickers(context: context)\n        let keychainPicker = try #require(pickers.first(where: { $0.id == \"claude-keychain-prompt-policy\" }))\n        #expect(keychainPicker.isEnabled?() == false)\n        let subtitle = keychainPicker.dynamicSubtitle?() ?? \"\"\n        #expect(subtitle.localizedCaseInsensitiveContains(\"inactive\"))\n    }\n\n    @Test\n    func `claude web extras auto disables when leaving CLI`() throws {\n        let suite = \"ProviderSettingsDescriptorTests-claude-invariant\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n        let settings = SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n        settings.debugMenuEnabled = true\n        settings.claudeUsageDataSource = .cli\n        settings.claudeWebExtrasEnabled = true\n\n        settings.claudeUsageDataSource = .oauth\n        #expect(settings.claudeWebExtrasEnabled == false)\n    }\n\n    @Test\n    func `kilo exposes usage source picker and api field only`() throws {\n        let suite = \"ProviderSettingsDescriptorTests-kilo\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n        let settings = SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n        let store = UsageStore(\n            fetcher: UsageFetcher(environment: [:]),\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            settings: settings)\n\n        let context = ProviderSettingsContext(\n            provider: .kilo,\n            settings: settings,\n            store: store,\n            boolBinding: { keyPath in\n                Binding(\n                    get: { settings[keyPath: keyPath] },\n                    set: { settings[keyPath: keyPath] = $0 })\n            },\n            stringBinding: { keyPath in\n                Binding(\n                    get: { settings[keyPath: keyPath] },\n                    set: { settings[keyPath: keyPath] = $0 })\n            },\n            statusText: { _ in nil },\n            setStatusText: { _, _ in },\n            lastAppActiveRunAt: { _ in nil },\n            setLastAppActiveRunAt: { _, _ in },\n            requestConfirmation: { _ in })\n\n        let implementation = KiloProviderImplementation()\n        let toggles = implementation.settingsToggles(context: context)\n        let pickers = implementation.settingsPickers(context: context)\n        let fields = implementation.settingsFields(context: context)\n\n        #expect(toggles.isEmpty)\n        #expect(pickers.contains(where: { $0.id == \"kilo-usage-source\" }))\n        #expect(fields.contains(where: { $0.id == \"kilo-api-key\" }))\n    }\n\n    @Test\n    func `alibaba presentation follows store source label`() throws {\n        let suite = \"ProviderSettingsDescriptorTests-alibaba-presentation\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n        let settings = SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n        let store = UsageStore(\n            fetcher: UsageFetcher(environment: [:]),\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            settings: settings)\n        let metadata = try #require(ProviderDescriptorRegistry.metadata[.alibaba])\n        let context = ProviderPresentationContext(\n            provider: .alibaba,\n            settings: settings,\n            store: store,\n            metadata: metadata)\n\n        let detailLine = AlibabaCodingPlanProviderImplementation()\n            .presentation(context: context)\n            .detailLine(context)\n\n        #expect(detailLine == store.sourceLabel(for: .alibaba))\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/ProviderToggleStoreTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBar\n\n@MainActor\nstruct ProviderToggleStoreTests {\n    @Test\n    func `defaults match metadata`() throws {\n        let defaults = try #require(UserDefaults(suiteName: \"ProviderToggleStoreTests-defaults\"))\n        defaults.removePersistentDomain(forName: \"ProviderToggleStoreTests-defaults\")\n        let store = ProviderToggleStore(userDefaults: defaults)\n        let registry = ProviderRegistry.shared\n        let codexMeta = try #require(registry.metadata[.codex])\n        let claudeMeta = try #require(registry.metadata[.claude])\n\n        #expect(store.isEnabled(metadata: codexMeta))\n        #expect(!store.isEnabled(metadata: claudeMeta))\n    }\n\n    @Test\n    func `persists changes`() throws {\n        let suite = \"ProviderToggleStoreTests-persist\"\n        let defaultsA = try #require(UserDefaults(suiteName: suite))\n        defaultsA.removePersistentDomain(forName: suite)\n        let storeA = ProviderToggleStore(userDefaults: defaultsA)\n        let registry = ProviderRegistry.shared\n        let claudeMeta = try #require(registry.metadata[.claude])\n\n        storeA.setEnabled(true, metadata: claudeMeta)\n\n        let defaultsB = try #require(UserDefaults(suiteName: suite))\n        let storeB = ProviderToggleStore(userDefaults: defaultsB)\n        #expect(storeB.isEnabled(metadata: claudeMeta))\n    }\n\n    @Test\n    func `purges legacy keys`() throws {\n        let suite = \"ProviderToggleStoreTests-purge\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        defaults.set(false, forKey: \"showCodexUsage\")\n        defaults.set(true, forKey: \"showClaudeUsage\")\n\n        let store = ProviderToggleStore(userDefaults: defaults)\n        store.purgeLegacyKeys()\n\n        #expect(defaults.object(forKey: \"showCodexUsage\") == nil)\n        #expect(defaults.object(forKey: \"showClaudeUsage\") == nil)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/ProviderTokenResolverTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n\nstruct ProviderTokenResolverTests {\n    @Test\n    func `zai resolution uses environment token`() {\n        let env = [ZaiSettingsReader.apiTokenKey: \"token\"]\n        let resolution = ProviderTokenResolver.zaiResolution(environment: env)\n        #expect(resolution?.token == \"token\")\n        #expect(resolution?.source == .environment)\n    }\n\n    @Test\n    func `copilot resolution trims token`() {\n        let env = [\"COPILOT_API_TOKEN\": \"  token  \"]\n        let resolution = ProviderTokenResolver.copilotResolution(environment: env)\n        #expect(resolution?.token == \"token\")\n    }\n\n    @Test\n    func `warp resolution uses environment token`() {\n        let env = [\"WARP_API_KEY\": \"wk-test-token\"]\n        let resolution = ProviderTokenResolver.warpResolution(environment: env)\n        #expect(resolution?.token == \"wk-test-token\")\n        #expect(resolution?.source == .environment)\n    }\n\n    @Test\n    func `warp resolution trims token`() {\n        let env = [\"WARP_API_KEY\": \"  wk-token  \"]\n        let resolution = ProviderTokenResolver.warpResolution(environment: env)\n        #expect(resolution?.token == \"wk-token\")\n    }\n\n    @Test\n    func `warp resolution returns nil when missing`() {\n        let env: [String: String] = [:]\n        let resolution = ProviderTokenResolver.warpResolution(environment: env)\n        #expect(resolution == nil)\n    }\n\n    @Test\n    func `kilo resolution prefers environment over auth file`() throws {\n        let fileURL = try self.makeKiloAuthFile(contents: #\"{\"kilo\":{\"access\":\"file-token\"}}\"#)\n        defer { try? FileManager.default.removeItem(at: fileURL.deletingLastPathComponent()) }\n\n        let env = [KiloSettingsReader.apiTokenKey: \"env-token\"]\n        let resolution = ProviderTokenResolver.kiloResolution(environment: env, authFileURL: fileURL)\n\n        #expect(resolution?.token == \"env-token\")\n        #expect(resolution?.source == .environment)\n    }\n\n    @Test\n    func `kilo resolution falls back to auth file`() throws {\n        let fileURL = try self.makeKiloAuthFile(contents: #\"{\"kilo\":{\"access\":\"file-token\"}}\"#)\n        defer { try? FileManager.default.removeItem(at: fileURL.deletingLastPathComponent()) }\n\n        let resolution = ProviderTokenResolver.kiloResolution(environment: [:], authFileURL: fileURL)\n\n        #expect(resolution?.token == \"file-token\")\n        #expect(resolution?.source == .authFile)\n    }\n\n    @Test\n    func `kilo resolution returns nil for malformed auth file`() throws {\n        let fileURL = try self.makeKiloAuthFile(contents: #\"{not-json}\"#)\n        defer { try? FileManager.default.removeItem(at: fileURL.deletingLastPathComponent()) }\n\n        let resolution = ProviderTokenResolver.kiloResolution(environment: [:], authFileURL: fileURL)\n        #expect(resolution == nil)\n    }\n\n    private func makeKiloAuthFile(contents: String) throws -> URL {\n        let directory = FileManager.default.temporaryDirectory\n            .appendingPathComponent(UUID().uuidString, isDirectory: true)\n        try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)\n        let fileURL = directory.appendingPathComponent(\"auth.json\", isDirectory: false)\n        try contents.write(to: fileURL, atomically: true, encoding: .utf8)\n        return fileURL\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/ProviderVersionDetectorTests.swift",
    "content": "import XCTest\n@testable import CodexBarCore\n\nfinal class ProviderVersionDetectorTests: XCTestCase {\n    func test_run_returnsFirstLineForSuccessfulCommand() {\n        let version = ProviderVersionDetector.run(\n            path: \"/bin/sh\",\n            args: [\"-c\", \"printf 'gemini 1.2.3\\\\nextra\\\\n'\"],\n            timeout: 1.0)\n\n        XCTAssertEqual(version, \"gemini 1.2.3\")\n    }\n\n    func test_run_returnsNilAfterTimeout() {\n        let start = Date()\n        let version = ProviderVersionDetector.run(\n            path: \"/bin/sh\",\n            args: [\"-c\", \"sleep 5\"],\n            timeout: 0.1)\n        let duration = Date().timeIntervalSince(start)\n\n        XCTAssertNil(version)\n        XCTAssertLessThan(duration, 2.0)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/ProvidersPaneCoverageTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n@testable import CodexBar\n\n@MainActor\nstruct ProvidersPaneCoverageTests {\n    @Test\n    func `exercises providers pane views`() {\n        let settings = Self.makeSettingsStore(suite: \"ProvidersPaneCoverageTests\")\n        let store = Self.makeUsageStore(settings: settings)\n\n        ProvidersPaneTestHarness.exercise(settings: settings, store: store)\n    }\n\n    @Test\n    func `open router menu bar metric picker shows only automatic and primary`() {\n        let settings = Self.makeSettingsStore(suite: \"ProvidersPaneCoverageTests-openrouter-picker\")\n        let store = Self.makeUsageStore(settings: settings)\n        let pane = ProvidersPane(settings: settings, store: store)\n\n        let picker = pane._test_menuBarMetricPicker(for: .openrouter)\n        #expect(picker?.options.map(\\.id) == [\n            MenuBarMetricPreference.automatic.rawValue,\n            MenuBarMetricPreference.primary.rawValue,\n        ])\n        #expect(picker?.options.map(\\.title) == [\n            \"Automatic\",\n            \"Primary (API key limit)\",\n        ])\n    }\n\n    @Test\n    func `provider detail plan row formats open router as balance`() {\n        let row = ProviderDetailView.planRow(provider: .openrouter, planText: \"Balance: $4.61\")\n\n        #expect(row?.label == \"Balance\")\n        #expect(row?.value == \"$4.61\")\n    }\n\n    @Test\n    func `provider detail plan row keeps plan label for non open router`() {\n        let row = ProviderDetailView.planRow(provider: .codex, planText: \"Pro\")\n\n        #expect(row?.label == \"Plan\")\n        #expect(row?.value == \"Pro\")\n    }\n\n    private static func makeSettingsStore(suite: String) -> SettingsStore {\n        let defaults = UserDefaults(suiteName: suite)!\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n\n        return SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore(),\n            codexCookieStore: InMemoryCookieHeaderStore(),\n            claudeCookieStore: InMemoryCookieHeaderStore(),\n            cursorCookieStore: InMemoryCookieHeaderStore(),\n            opencodeCookieStore: InMemoryCookieHeaderStore(),\n            factoryCookieStore: InMemoryCookieHeaderStore(),\n            minimaxCookieStore: InMemoryMiniMaxCookieStore(),\n            minimaxAPITokenStore: InMemoryMiniMaxAPITokenStore(),\n            kimiTokenStore: InMemoryKimiTokenStore(),\n            kimiK2TokenStore: InMemoryKimiK2TokenStore(),\n            augmentCookieStore: InMemoryCookieHeaderStore(),\n            ampCookieStore: InMemoryCookieHeaderStore(),\n            copilotTokenStore: InMemoryCopilotTokenStore(),\n            tokenAccountStore: InMemoryTokenAccountStore())\n    }\n\n    private static func makeUsageStore(settings: SettingsStore) -> UsageStore {\n        UsageStore(\n            fetcher: UsageFetcher(environment: [:]),\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            settings: settings)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/SessionQuotaNotificationLogicTests.swift",
    "content": "import Testing\n@testable import CodexBar\n\nstruct SessionQuotaNotificationLogicTests {\n    @Test\n    func `does nothing without previous value`() {\n        let transition = SessionQuotaNotificationLogic.transition(previousRemaining: nil, currentRemaining: 0)\n        #expect(transition == .none)\n    }\n\n    @Test\n    func `detects depleted transition`() {\n        let transition = SessionQuotaNotificationLogic.transition(previousRemaining: 12, currentRemaining: 0)\n        #expect(transition == .depleted)\n    }\n\n    @Test\n    func `detects restored transition`() {\n        let transition = SessionQuotaNotificationLogic.transition(previousRemaining: 0, currentRemaining: 5)\n        #expect(transition == .restored)\n    }\n\n    @Test\n    func `ignores non transitions`() {\n        #expect(SessionQuotaNotificationLogic.transition(previousRemaining: 0, currentRemaining: 0) == .none)\n        #expect(SessionQuotaNotificationLogic.transition(previousRemaining: 10, currentRemaining: 10) == .none)\n        #expect(SessionQuotaNotificationLogic.transition(previousRemaining: 10, currentRemaining: 9) == .none)\n    }\n\n    @Test\n    func `treats tiny positive remaining as depleted`() {\n        let transition = SessionQuotaNotificationLogic.transition(previousRemaining: 0, currentRemaining: 0.00001)\n        #expect(transition == .none)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/SettingsStoreAdditionalTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n@testable import CodexBar\n\n@MainActor\nstruct SettingsStoreAdditionalTests {\n    @Test\n    func `menu bar metric preference handles zai and average`() {\n        let settings = Self.makeSettingsStore(suite: \"SettingsStoreAdditionalTests-metric\")\n\n        settings.setMenuBarMetricPreference(.average, for: .zai)\n        #expect(settings.menuBarMetricPreference(for: .zai) == .primary)\n\n        settings.setMenuBarMetricPreference(.average, for: .codex)\n        #expect(settings.menuBarMetricPreference(for: .codex) == .automatic)\n\n        settings.setMenuBarMetricPreference(.average, for: .gemini)\n        #expect(settings.menuBarMetricPreference(for: .gemini) == .average)\n    }\n\n    @Test\n    func `menu bar metric preference restricts open router to automatic or primary`() {\n        let settings = Self.makeSettingsStore(suite: \"SettingsStoreAdditionalTests-openrouter-metric\")\n\n        settings.setMenuBarMetricPreference(.secondary, for: .openrouter)\n        #expect(settings.menuBarMetricPreference(for: .openrouter) == .automatic)\n\n        settings.setMenuBarMetricPreference(.average, for: .openrouter)\n        #expect(settings.menuBarMetricPreference(for: .openrouter) == .automatic)\n\n        settings.setMenuBarMetricPreference(.primary, for: .openrouter)\n        #expect(settings.menuBarMetricPreference(for: .openrouter) == .primary)\n    }\n\n    @Test\n    func `minimax auth mode uses stored values`() {\n        let settings = Self.makeSettingsStore(suite: \"SettingsStoreAdditionalTests-minimax\")\n        settings.minimaxAPIToken = \"sk-api-test-token\"\n        settings.minimaxCookieHeader = \"cookie=value\"\n\n        #expect(settings.minimaxAuthMode(environment: [:]) == .apiToken)\n\n        settings.minimaxAPIToken = \"\"\n        #expect(settings.minimaxAuthMode(environment: [:]) == .cookie)\n    }\n\n    @Test\n    func `token accounts set manual cookie source when required`() {\n        let settings = Self.makeSettingsStore(suite: \"SettingsStoreAdditionalTests-token-accounts\")\n\n        settings.addTokenAccount(provider: .claude, label: \"Primary\", token: \"token-1\")\n\n        #expect(settings.tokenAccounts(for: .claude).count == 1)\n        #expect(settings.claudeCookieSource == .manual)\n    }\n\n    @Test\n    func `ollama token accounts set manual cookie source when required`() {\n        let settings = Self.makeSettingsStore(suite: \"SettingsStoreAdditionalTests-ollama-token-accounts\")\n\n        settings.addTokenAccount(provider: .ollama, label: \"Primary\", token: \"session=token-1\")\n\n        #expect(settings.tokenAccounts(for: .ollama).count == 1)\n        #expect(settings.ollamaCookieSource == .manual)\n    }\n\n    @Test\n    func `detects token cost usage sources from filesystem`() throws {\n        let fm = FileManager.default\n        let root = fm.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)\n        let sessions = root.appendingPathComponent(\"sessions\", isDirectory: true)\n        try fm.createDirectory(at: sessions, withIntermediateDirectories: true)\n        let jsonl = sessions.appendingPathComponent(\"usage.jsonl\")\n        try Data(\"{}\".utf8).write(to: jsonl)\n        defer { try? fm.removeItem(at: root) }\n\n        let env = [\"CODEX_HOME\": root.path]\n\n        #expect(SettingsStore.hasAnyTokenCostUsageSources(env: env, fileManager: fm))\n    }\n\n    private static func makeSettingsStore(suite: String) -> SettingsStore {\n        let defaults = UserDefaults(suiteName: suite)!\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n\n        return SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore(),\n            codexCookieStore: InMemoryCookieHeaderStore(),\n            claudeCookieStore: InMemoryCookieHeaderStore(),\n            cursorCookieStore: InMemoryCookieHeaderStore(),\n            opencodeCookieStore: InMemoryCookieHeaderStore(),\n            factoryCookieStore: InMemoryCookieHeaderStore(),\n            minimaxCookieStore: InMemoryMiniMaxCookieStore(),\n            minimaxAPITokenStore: InMemoryMiniMaxAPITokenStore(),\n            kimiTokenStore: InMemoryKimiTokenStore(),\n            kimiK2TokenStore: InMemoryKimiK2TokenStore(),\n            augmentCookieStore: InMemoryCookieHeaderStore(),\n            ampCookieStore: InMemoryCookieHeaderStore(),\n            copilotTokenStore: InMemoryCopilotTokenStore(),\n            tokenAccountStore: InMemoryTokenAccountStore())\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/SettingsStoreCoverageTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n@testable import CodexBar\n\n@MainActor\nstruct SettingsStoreCoverageTests {\n    @Test\n    func `provider ordering and caching`() throws {\n        let suite = \"SettingsStoreCoverageTests-ordering\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n        let config = CodexBarConfig(providers: [\n            ProviderConfig(id: .zai),\n            ProviderConfig(id: .codex),\n            ProviderConfig(id: .claude),\n        ])\n        try configStore.save(config)\n        let settings = Self.makeSettingsStore(userDefaults: defaults, configStore: configStore)\n        let ordered = settings.orderedProviders()\n        let cached = settings.orderedProviders()\n\n        #expect(ordered == cached)\n        #expect(ordered.first == .zai)\n        #expect(ordered.contains(.minimax))\n\n        settings.moveProvider(fromOffsets: IndexSet(integer: 0), toOffset: 2)\n        #expect(settings.orderedProviders() != ordered)\n\n        let metadata = ProviderRegistry.shared.metadata\n        try settings.setProviderEnabled(provider: .codex, metadata: #require(metadata[.codex]), enabled: true)\n        try settings.setProviderEnabled(provider: .claude, metadata: #require(metadata[.claude]), enabled: false)\n        let enabled = settings.enabledProvidersOrdered(metadataByProvider: metadata)\n        #expect(enabled.contains(.codex))\n    }\n\n    @Test\n    func `menu bar metric preferences and display modes`() {\n        let settings = Self.makeSettingsStore()\n\n        settings.setMenuBarMetricPreference(.average, for: .codex)\n        #expect(settings.menuBarMetricPreference(for: .codex) == .automatic)\n\n        settings.setMenuBarMetricPreference(.average, for: .gemini)\n        #expect(settings.menuBarMetricPreference(for: .gemini) == .average)\n        #expect(settings.menuBarMetricSupportsAverage(for: .gemini))\n\n        settings.setMenuBarMetricPreference(.secondary, for: .zai)\n        #expect(settings.menuBarMetricPreference(for: .zai) == .primary)\n\n        settings.menuBarDisplayMode = .pace\n        #expect(settings.menuBarDisplayMode == .pace)\n        #expect(settings.historicalTrackingEnabled == false)\n        settings.historicalTrackingEnabled = true\n        #expect(settings.historicalTrackingEnabled == true)\n\n        settings.resetTimesShowAbsolute = true\n        #expect(settings.resetTimeDisplayStyle == .absolute)\n    }\n\n    @Test\n    func `token account mutations apply side effects`() {\n        let settings = Self.makeSettingsStore()\n\n        settings.addTokenAccount(provider: .claude, label: \"Primary\", token: \"token\")\n        #expect(settings.tokenAccounts(for: .claude).count == 1)\n        #expect(settings.claudeCookieSource == .manual)\n\n        let account = settings.selectedTokenAccount(for: .claude)\n        #expect(account != nil)\n\n        settings.setActiveTokenAccountIndex(10, for: .claude)\n        #expect(settings.selectedTokenAccount(for: .claude)?.id == account?.id)\n\n        if let id = account?.id {\n            settings.removeTokenAccount(provider: .claude, accountID: id)\n        }\n        #expect(settings.tokenAccounts(for: .claude).isEmpty)\n\n        settings.reloadTokenAccounts()\n    }\n\n    @Test\n    func `claude snapshot uses OAuth routing for OAuth token accounts`() {\n        let settings = Self.makeSettingsStore()\n        settings.addTokenAccount(provider: .claude, label: \"OAuth\", token: \"Bearer sk-ant-oat-account-token\")\n\n        let snapshot = settings.claudeSettingsSnapshot(tokenOverride: nil)\n\n        #expect(snapshot.usageDataSource == .auto)\n        #expect(snapshot.cookieSource == .off)\n        #expect(snapshot.manualCookieHeader?.isEmpty == true)\n    }\n\n    @Test\n    func `claude snapshot uses manual cookie routing for session key accounts`() {\n        let settings = Self.makeSettingsStore()\n        settings.addTokenAccount(provider: .claude, label: \"Cookie\", token: \"sk-ant-session-token\")\n\n        let snapshot = settings.claudeSettingsSnapshot(tokenOverride: nil)\n\n        #expect(snapshot.usageDataSource == .auto)\n        #expect(snapshot.cookieSource == .manual)\n        #expect(snapshot.manualCookieHeader == \"sessionKey=sk-ant-session-token\")\n    }\n\n    @Test\n    func `claude snapshot normalizes config manual cookie input through shared route`() {\n        let settings = Self.makeSettingsStore()\n        settings.claudeCookieSource = .manual\n        settings.claudeCookieHeader = \"Cookie: sessionKey=sk-ant-session-token; foo=bar\"\n\n        let snapshot = settings.claudeSettingsSnapshot(tokenOverride: nil)\n\n        #expect(snapshot.usageDataSource == .auto)\n        #expect(snapshot.cookieSource == .manual)\n        #expect(snapshot.manualCookieHeader == \"sessionKey=sk-ant-session-token; foo=bar\")\n    }\n\n    @Test\n    func `claude snapshot does not fall back to config cookie for malformed selected token account`() {\n        let settings = Self.makeSettingsStore()\n        settings.claudeCookieSource = .manual\n        settings.claudeCookieHeader = \"Cookie: sessionKey=sk-ant-config-cookie\"\n        settings.addTokenAccount(provider: .claude, label: \"Malformed\", token: \"Cookie:\")\n\n        let snapshot = settings.claudeSettingsSnapshot(tokenOverride: nil)\n\n        #expect(snapshot.cookieSource == .manual)\n        #expect(snapshot.manualCookieHeader?.isEmpty == true)\n    }\n\n    @Test\n    func `token cost usage source detection`() throws {\n        let fileManager = FileManager.default\n        let root = fileManager.temporaryDirectory.appendingPathComponent(\n            \"token-cost-\\(UUID().uuidString)\",\n            isDirectory: true)\n        let codexRoot = root.appendingPathComponent(\"sessions\", isDirectory: true)\n        try fileManager.createDirectory(at: codexRoot, withIntermediateDirectories: true)\n        let codexFile = codexRoot.appendingPathComponent(\"usage.jsonl\")\n        fileManager.createFile(atPath: codexFile.path, contents: Data(\"{}\".utf8))\n\n        #expect(SettingsStore.hasAnyTokenCostUsageSources(\n            env: [\"CODEX_HOME\": root.path],\n            fileManager: fileManager))\n\n        let claudeRoot = fileManager.temporaryDirectory.appendingPathComponent(\n            \"claude-\\(UUID().uuidString)\",\n            isDirectory: true)\n        let claudeProjects = claudeRoot.appendingPathComponent(\"projects\", isDirectory: true)\n        try fileManager.createDirectory(at: claudeProjects, withIntermediateDirectories: true)\n        let claudeFile = claudeProjects.appendingPathComponent(\"usage.jsonl\")\n        fileManager.createFile(atPath: claudeFile.path, contents: Data(\"{}\".utf8))\n\n        #expect(SettingsStore.hasAnyTokenCostUsageSources(\n            env: [\"CLAUDE_CONFIG_DIR\": claudeRoot.path],\n            fileManager: fileManager))\n    }\n\n    @Test\n    func `ensure token loaders execute`() {\n        let settings = Self.makeSettingsStore()\n\n        settings.ensureZaiAPITokenLoaded()\n        settings.ensureSyntheticAPITokenLoaded()\n        settings.ensureCodexCookieLoaded()\n        settings.ensureClaudeCookieLoaded()\n        settings.ensureCursorCookieLoaded()\n        settings.ensureOpenCodeCookieLoaded()\n        settings.ensureFactoryCookieLoaded()\n        settings.ensureMiniMaxCookieLoaded()\n        settings.ensureMiniMaxAPITokenLoaded()\n        settings.ensureKimiAuthTokenLoaded()\n        settings.ensureKimiK2APITokenLoaded()\n        settings.ensureAugmentCookieLoaded()\n        settings.ensureAmpCookieLoaded()\n        settings.ensureOllamaCookieLoaded()\n        settings.ensureCopilotAPITokenLoaded()\n        settings.ensureTokenAccountsLoaded()\n\n        #expect(settings.zaiAPIToken.isEmpty)\n        #expect(settings.syntheticAPIToken.isEmpty)\n    }\n\n    @Test\n    func `keychain disable forces manual cookie sources`() throws {\n        let suite = \"SettingsStoreCoverageTests-keychain\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n        let settings = Self.makeSettingsStore(userDefaults: defaults, configStore: configStore)\n\n        settings.codexCookieSource = .auto\n        settings.claudeCookieSource = .auto\n        settings.kimiCookieSource = .off\n        settings.debugDisableKeychainAccess = true\n\n        #expect(settings.codexCookieSource == .manual)\n        #expect(settings.claudeCookieSource == .manual)\n        #expect(settings.kimiCookieSource == .off)\n    }\n\n    @Test\n    func `claude keychain prompt mode defaults to only on user action`() {\n        let settings = Self.makeSettingsStore()\n        #expect(settings.claudeOAuthKeychainPromptMode == .onlyOnUserAction)\n    }\n\n    @Test\n    func `claude keychain prompt mode persists across store reload`() throws {\n        let suite = \"SettingsStoreCoverageTests-claude-keychain-prompt-mode\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n\n        let first = Self.makeSettingsStore(userDefaults: defaults, configStore: configStore)\n        first.claudeOAuthKeychainPromptMode = .never\n        #expect(\n            defaults.string(forKey: \"claudeOAuthKeychainPromptMode\")\n                == ClaudeOAuthKeychainPromptMode.never.rawValue)\n\n        let second = Self.makeSettingsStore(userDefaults: defaults, configStore: configStore)\n        #expect(second.claudeOAuthKeychainPromptMode == .never)\n    }\n\n    @Test\n    func `claude keychain prompt mode invalid raw falls back to only on user action`() throws {\n        let suite = \"SettingsStoreCoverageTests-claude-keychain-prompt-mode-invalid\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        defaults.set(\"invalid-mode\", forKey: \"claudeOAuthKeychainPromptMode\")\n        let configStore = testConfigStore(suiteName: suite)\n\n        let settings = Self.makeSettingsStore(userDefaults: defaults, configStore: configStore)\n        #expect(settings.claudeOAuthKeychainPromptMode == .onlyOnUserAction)\n    }\n\n    @Test\n    func `claude keychain read strategy defaults to security framework`() {\n        let settings = Self.makeSettingsStore()\n        #expect(settings.claudeOAuthKeychainReadStrategy == .securityFramework)\n    }\n\n    @Test\n    func `claude keychain read strategy persists across store reload`() throws {\n        let suite = \"SettingsStoreCoverageTests-claude-keychain-read-strategy\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n\n        let first = Self.makeSettingsStore(userDefaults: defaults, configStore: configStore)\n        first.claudeOAuthKeychainReadStrategy = .securityCLIExperimental\n        #expect(\n            defaults.string(forKey: \"claudeOAuthKeychainReadStrategy\")\n                == ClaudeOAuthKeychainReadStrategy.securityCLIExperimental.rawValue)\n\n        let second = Self.makeSettingsStore(userDefaults: defaults, configStore: configStore)\n        #expect(second.claudeOAuthKeychainReadStrategy == .securityCLIExperimental)\n    }\n\n    @Test\n    func `claude keychain read strategy invalid raw falls back to security framework`() throws {\n        let suite = \"SettingsStoreCoverageTests-claude-keychain-read-strategy-invalid\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        defaults.set(\"invalid-strategy\", forKey: \"claudeOAuthKeychainReadStrategy\")\n        let configStore = testConfigStore(suiteName: suite)\n\n        let settings = Self.makeSettingsStore(userDefaults: defaults, configStore: configStore)\n        #expect(settings.claudeOAuthKeychainReadStrategy == .securityFramework)\n    }\n\n    @Test\n    func `claude prompt free credentials toggle maps to read strategy`() {\n        let settings = Self.makeSettingsStore()\n        #expect(settings.claudeOAuthPromptFreeCredentialsEnabled == false)\n\n        settings.claudeOAuthPromptFreeCredentialsEnabled = true\n        #expect(settings.claudeOAuthKeychainReadStrategy == .securityCLIExperimental)\n\n        settings.claudeOAuthPromptFreeCredentialsEnabled = false\n        #expect(settings.claudeOAuthKeychainReadStrategy == .securityFramework)\n    }\n\n    private static func makeSettingsStore(suiteName: String = \"SettingsStoreCoverageTests\") -> SettingsStore {\n        let defaults = UserDefaults(suiteName: suiteName)!\n        defaults.removePersistentDomain(forName: suiteName)\n        defaults.set(false, forKey: \"debugDisableKeychainAccess\")\n        let configStore = testConfigStore(suiteName: suiteName)\n        return Self.makeSettingsStore(userDefaults: defaults, configStore: configStore)\n    }\n\n    private static func makeSettingsStore(\n        userDefaults: UserDefaults,\n        configStore: CodexBarConfigStore) -> SettingsStore\n    {\n        SettingsStore(\n            userDefaults: userDefaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore(),\n            codexCookieStore: InMemoryCookieHeaderStore(),\n            claudeCookieStore: InMemoryCookieHeaderStore(),\n            cursorCookieStore: InMemoryCookieHeaderStore(),\n            opencodeCookieStore: InMemoryCookieHeaderStore(),\n            factoryCookieStore: InMemoryCookieHeaderStore(),\n            minimaxCookieStore: InMemoryMiniMaxCookieStore(),\n            minimaxAPITokenStore: InMemoryMiniMaxAPITokenStore(),\n            kimiTokenStore: InMemoryKimiTokenStore(),\n            kimiK2TokenStore: InMemoryKimiK2TokenStore(),\n            augmentCookieStore: InMemoryCookieHeaderStore(),\n            ampCookieStore: InMemoryCookieHeaderStore(),\n            copilotTokenStore: InMemoryCopilotTokenStore(),\n            tokenAccountStore: InMemoryTokenAccountStore())\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/SettingsStoreTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Observation\nimport Testing\n@testable import CodexBar\n\n@MainActor\nstruct SettingsStoreTests {\n    @Test\n    func `default refresh frequency is five minutes`() throws {\n        let suite = \"SettingsStoreTests-default\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n\n        let store = SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        #expect(store.refreshFrequency == .fiveMinutes)\n        #expect(store.refreshFrequency.seconds == 300)\n    }\n\n    @Test\n    func `persists refresh frequency across instances`() throws {\n        let suite = \"SettingsStoreTests-persist\"\n        let defaultsA = try #require(UserDefaults(suiteName: suite))\n        defaultsA.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n        let storeA = SettingsStore(\n            userDefaults: defaultsA,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        storeA.refreshFrequency = .fifteenMinutes\n\n        let defaultsB = try #require(UserDefaults(suiteName: suite))\n        let storeB = SettingsStore(\n            userDefaults: defaultsB,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        #expect(storeB.refreshFrequency == .fifteenMinutes)\n        #expect(storeB.refreshFrequency.seconds == 900)\n    }\n\n    @Test\n    func `persists selected menu provider across instances`() throws {\n        let suite = \"SettingsStoreTests-selectedMenuProvider\"\n        let defaultsA = try #require(UserDefaults(suiteName: suite))\n        defaultsA.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n        let storeA = SettingsStore(\n            userDefaults: defaultsA,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        storeA.selectedMenuProvider = .claude\n\n        let defaultsB = try #require(UserDefaults(suiteName: suite))\n        let storeB = SettingsStore(\n            userDefaults: defaultsB,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        #expect(storeB.selectedMenuProvider == .claude)\n    }\n\n    @Test\n    func `persists merged menu last selected was overview across instances`() throws {\n        let suite = \"SettingsStoreTests-merged-last-overview\"\n        let defaultsA = try #require(UserDefaults(suiteName: suite))\n        defaultsA.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n        let storeA = SettingsStore(\n            userDefaults: defaultsA,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        storeA.mergedMenuLastSelectedWasOverview = true\n\n        let defaultsB = try #require(UserDefaults(suiteName: suite))\n        let storeB = SettingsStore(\n            userDefaults: defaultsB,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        #expect(storeB.mergedMenuLastSelectedWasOverview == true)\n    }\n\n    @Test\n    func `merged overview selected providers persists and normalizes across instances`() throws {\n        let suite = \"SettingsStoreTests-merged-overview-selection\"\n        let defaultsA = try #require(UserDefaults(suiteName: suite))\n        defaultsA.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n        let storeA = SettingsStore(\n            userDefaults: defaultsA,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        storeA.mergedOverviewSelectedProviders = [.opencode, .codex, .opencode, .claude]\n        #expect(storeA.mergedOverviewSelectedProviders == [.opencode, .codex, .claude])\n\n        let defaultsB = try #require(UserDefaults(suiteName: suite))\n        let storeB = SettingsStore(\n            userDefaults: defaultsB,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        #expect(storeB.mergedOverviewSelectedProviders == [.opencode, .codex, .claude])\n    }\n\n    @Test\n    func `merged overview selected providers ignores invalid raw values`() throws {\n        let suite = \"SettingsStoreTests-merged-overview-invalid-raw\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        defaults.set([\"codex\", \"unknown-provider\", \"claude\", \"codex\"], forKey: \"mergedOverviewSelectedProviders\")\n        let configStore = testConfigStore(suiteName: suite)\n        let store = SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        #expect(store.mergedOverviewSelectedProviders == [.codex, .claude])\n    }\n\n    @Test\n    func `resolved merged overview providers defaults to first three when selection empty`() throws {\n        let suite = \"SettingsStoreTests-merged-overview-default-first-three\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n        let store = SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        let activeProviders: [UsageProvider] = [.codex, .claude, .cursor, .opencode, .warp]\n        let resolved = store.resolvedMergedOverviewProviders(activeProviders: activeProviders)\n\n        #expect(resolved == [.codex, .claude, .cursor])\n    }\n\n    @Test\n    func `resolved merged overview providers honors explicit empty selection`() throws {\n        let suite = \"SettingsStoreTests-merged-overview-explicit-empty\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n        let store = SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        store.mergedOverviewSelectedProviders = []\n        let activeProviders: [UsageProvider] = [.codex, .claude, .cursor, .opencode, .warp]\n        let resolved = store.resolvedMergedOverviewProviders(activeProviders: activeProviders)\n\n        #expect(resolved == [])\n    }\n\n    @Test\n    func `resolved merged overview providers uses provider order not selection order`() throws {\n        let suite = \"SettingsStoreTests-merged-overview-order\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n        let store = SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        store.mergedOverviewSelectedProviders = [.opencode, .codex, .cursor]\n        let activeProviders: [UsageProvider] = [.codex, .claude, .cursor, .opencode]\n        let resolved = store.resolvedMergedOverviewProviders(activeProviders: activeProviders)\n\n        #expect(resolved == [.codex, .cursor, .opencode])\n    }\n\n    @Test\n    func `reconcile merged overview selection removes unavailable without auto fill`() throws {\n        let suite = \"SettingsStoreTests-merged-overview-reconcile\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n        let store = SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        store.mergedOverviewSelectedProviders = [.codex, .claude, .opencode]\n        let activeProviders: [UsageProvider] = [.codex, .cursor, .gemini, .opencode]\n\n        let resolved = store.reconcileMergedOverviewSelectedProviders(activeProviders: activeProviders)\n\n        #expect(resolved == [.codex, .opencode])\n        #expect(store.mergedOverviewSelectedProviders == [.codex, .opencode])\n    }\n\n    @Test\n    func `reconcile merged overview selection does not clobber stored preference when three or fewer`() throws {\n        let suite = \"SettingsStoreTests-merged-overview-three-or-fewer\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n        let store = SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        store.mergedOverviewSelectedProviders = [.codex, .claude, .cursor]\n        let activeProviders: [UsageProvider] = [.codex, .claude]\n\n        let resolved = store.reconcileMergedOverviewSelectedProviders(activeProviders: activeProviders)\n\n        #expect(resolved == [.codex, .claude])\n        #expect(store.mergedOverviewSelectedProviders == [.codex, .claude, .cursor])\n    }\n\n    @Test\n    func `reconcile merged overview selection ignores stale subset without persisting auto fill when three or fewer`()\n        throws\n    {\n        let suite = \"SettingsStoreTests-merged-overview-three-or-fewer-subset\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n        let store = SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        store.mergedOverviewSelectedProviders = [.codex]\n        let activeProviders: [UsageProvider] = [.codex, .claude, .cursor]\n\n        let resolved = store.reconcileMergedOverviewSelectedProviders(activeProviders: activeProviders)\n\n        #expect(resolved == [.codex, .claude, .cursor])\n        #expect(store.mergedOverviewSelectedProviders == [.codex])\n    }\n\n    @Test\n    func `merged overview selection allows deselecting providers when three or fewer`() throws {\n        let suite = \"SettingsStoreTests-merged-overview-deselect-three-or-fewer\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n        let store = SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        let activeProviders: [UsageProvider] = [.codex, .claude, .cursor]\n        #expect(store.resolvedMergedOverviewProviders(activeProviders: activeProviders) == activeProviders)\n\n        _ = store.setMergedOverviewProviderSelection(\n            provider: .claude,\n            isSelected: false,\n            activeProviders: activeProviders)\n\n        #expect(store.mergedOverviewSelectedProviders == [.codex, .cursor])\n        #expect(store.resolvedMergedOverviewProviders(activeProviders: activeProviders) == [.codex, .cursor])\n    }\n\n    @Test\n    func `merged overview selection applies when same active set is reordered`() throws {\n        let suite = \"SettingsStoreTests-merged-overview-ordered-context\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n        let store = SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        let initialActiveProviders: [UsageProvider] = [.codex, .claude, .cursor]\n        _ = store.setMergedOverviewProviderSelection(\n            provider: .claude,\n            isSelected: false,\n            activeProviders: initialActiveProviders)\n\n        let reorderedActiveProviders: [UsageProvider] = [.cursor, .codex, .claude]\n        let resolved = store.resolvedMergedOverviewProviders(activeProviders: reorderedActiveProviders)\n\n        #expect(resolved == [.cursor, .codex])\n    }\n\n    @Test\n    func `merged overview selection allows deselecting providers when more than three active`() throws {\n        let suite = \"SettingsStoreTests-merged-overview-deselect-subset\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n        let store = SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        store.mergedOverviewSelectedProviders = [.codex, .claude, .cursor]\n        let activeProviders: [UsageProvider] = [.codex, .claude, .cursor, .opencode]\n\n        _ = store.setMergedOverviewProviderSelection(\n            provider: .cursor,\n            isSelected: false,\n            activeProviders: activeProviders)\n\n        #expect(store.mergedOverviewSelectedProviders == [.codex, .claude])\n        #expect(store.resolvedMergedOverviewProviders(activeProviders: activeProviders) == [.codex, .claude])\n    }\n\n    @Test\n    func `reconcile merged overview selection preserves stored subset when active drops to three or fewer`() throws {\n        let suite = \"SettingsStoreTests-merged-overview-preserve-subset-across-drop\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n        let store = SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        let activeProviders: [UsageProvider] = [.codex, .claude, .cursor, .opencode]\n        _ = store.setMergedOverviewProviderSelection(\n            provider: .claude,\n            isSelected: false,\n            activeProviders: activeProviders)\n        _ = store.setMergedOverviewProviderSelection(\n            provider: .opencode,\n            isSelected: true,\n            activeProviders: activeProviders)\n        #expect(store.mergedOverviewSelectedProviders == [.codex, .cursor, .opencode])\n\n        let reducedActiveProviders: [UsageProvider] = [.codex, .claude, .cursor]\n        let resolvedWhenReduced = store.reconcileMergedOverviewSelectedProviders(\n            activeProviders: reducedActiveProviders)\n\n        #expect(resolvedWhenReduced == [.codex, .claude, .cursor])\n        #expect(store.mergedOverviewSelectedProviders == [.codex, .cursor, .opencode])\n\n        let resolvedWhenRestored = store.resolvedMergedOverviewProviders(activeProviders: activeProviders)\n        #expect(resolvedWhenRestored == [.codex, .cursor, .opencode])\n    }\n\n    @Test\n    func `reconcile merged overview selection clears preference when no providers active`() throws {\n        let suite = \"SettingsStoreTests-merged-overview-clear-on-empty-active\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n        let store = SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        let activeProviders: [UsageProvider] = [.codex, .claude, .cursor, .opencode]\n        _ = store.setMergedOverviewProviderSelection(\n            provider: .codex,\n            isSelected: false,\n            activeProviders: activeProviders)\n        #expect(store.resolvedMergedOverviewProviders(activeProviders: activeProviders) == [.claude, .cursor])\n\n        let resolvedWhenEmpty = store.reconcileMergedOverviewSelectedProviders(activeProviders: [])\n        #expect(resolvedWhenEmpty == [])\n\n        let resolvedAfterReenable = store.resolvedMergedOverviewProviders(activeProviders: activeProviders)\n        #expect(resolvedAfterReenable == [.codex, .claude, .cursor])\n    }\n\n    @Test\n    func `persists open code workspace ID across instances`() throws {\n        let suite = \"SettingsStoreTests-opencode-workspace\"\n        let defaultsA = try #require(UserDefaults(suiteName: suite))\n        defaultsA.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n        let storeA = SettingsStore(\n            userDefaults: defaultsA,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore())\n\n        storeA.opencodeWorkspaceID = \"wrk_01KEJ50SHK9YR41HSRSJ6QTFCM\"\n\n        let defaultsB = try #require(UserDefaults(suiteName: suite))\n        let storeB = SettingsStore(\n            userDefaults: defaultsB,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore())\n\n        #expect(storeB.opencodeWorkspaceID == \"wrk_01KEJ50SHK9YR41HSRSJ6QTFCM\")\n    }\n\n    @Test\n    func `defaults session quota notifications to enabled`() throws {\n        let key = \"sessionQuotaNotificationsEnabled\"\n        let suite = \"SettingsStoreTests-sessionQuotaNotifications\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n        let store = SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n        #expect(store.sessionQuotaNotificationsEnabled == true)\n        #expect(defaults.bool(forKey: key) == true)\n    }\n\n    @Test\n    func `defaults claude usage source to auto`() throws {\n        let suite = \"SettingsStoreTests-claude-source\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n\n        let store = SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        #expect(store.claudeUsageDataSource == .auto)\n    }\n\n    @Test\n    func `defaults codex usage source to auto`() throws {\n        let suite = \"SettingsStoreTests-codex-source\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n\n        let store = SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        #expect(store.codexUsageDataSource == .auto)\n    }\n\n    @Test\n    func `defaults kilo usage source to auto`() throws {\n        let suite = \"SettingsStoreTests-kilo-source\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n\n        let store = SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        #expect(store.kiloUsageDataSource == .auto)\n    }\n\n    @Test\n    func `persists kilo usage source across instances`() throws {\n        let suite = \"SettingsStoreTests-kilo-source-persist\"\n        let defaultsA = try #require(UserDefaults(suiteName: suite))\n        defaultsA.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n        let storeA = SettingsStore(\n            userDefaults: defaultsA,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        storeA.kiloUsageDataSource = .cli\n\n        let defaultsB = try #require(UserDefaults(suiteName: suite))\n        let storeB = SettingsStore(\n            userDefaults: defaultsB,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        #expect(storeB.kiloUsageDataSource == .cli)\n    }\n\n    @Test\n    func `kilo extras only apply in auto mode`() throws {\n        let suite = \"SettingsStoreTests-kilo-extras\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n        let store = SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        store.kiloExtrasEnabled = true\n        #expect(store.kiloExtrasEnabled)\n\n        store.kiloUsageDataSource = .api\n        #expect(!store.kiloExtrasEnabled)\n\n        store.kiloUsageDataSource = .auto\n        #expect(store.kiloExtrasEnabled)\n    }\n\n    @Test\n    @MainActor\n    func `apply external config does not broadcast`() throws {\n        let suite = \"SettingsStoreTests-external-config\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n        let store = SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        final class NotificationCounter: @unchecked Sendable {\n            private let lock = NSLock()\n            private var value = 0\n\n            func increment() {\n                self.lock.lock()\n                self.value += 1\n                self.lock.unlock()\n            }\n\n            func get() -> Int {\n                self.lock.lock()\n                defer { self.lock.unlock() }\n                return self.value\n            }\n        }\n\n        let notifications = NotificationCounter()\n        let token = NotificationCenter.default.addObserver(\n            forName: .codexbarProviderConfigDidChange,\n            object: store,\n            queue: .main)\n        { _ in\n            notifications.increment()\n        }\n        defer { NotificationCenter.default.removeObserver(token) }\n\n        store.applyExternalConfig(store.configSnapshot, reason: \"test-external\")\n\n        #expect(notifications.get() == 0)\n    }\n\n    @Test\n    func `persists zai API region across instances`() throws {\n        let suite = \"SettingsStoreTests-zai-region\"\n        let defaultsA = try #require(UserDefaults(suiteName: suite))\n        defaultsA.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n        let storeA = SettingsStore(\n            userDefaults: defaultsA,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore())\n\n        storeA.zaiAPIRegion = .bigmodelCN\n\n        let defaultsB = try #require(UserDefaults(suiteName: suite))\n        let storeB = SettingsStore(\n            userDefaults: defaultsB,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore())\n\n        #expect(storeB.zaiAPIRegion == .bigmodelCN)\n    }\n\n    @Test\n    func `persists mini max API region across instances`() throws {\n        let suite = \"SettingsStoreTests-minimax-region\"\n        let defaultsA = try #require(UserDefaults(suiteName: suite))\n        defaultsA.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n        let storeA = SettingsStore(\n            userDefaults: defaultsA,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore())\n\n        storeA.minimaxAPIRegion = .chinaMainland\n\n        let defaultsB = try #require(UserDefaults(suiteName: suite))\n        let storeB = SettingsStore(\n            userDefaults: defaultsB,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore())\n\n        #expect(storeB.minimaxAPIRegion == .chinaMainland)\n    }\n\n    @Test\n    func `defaults open AI web access to enabled`() throws {\n        let suite = \"SettingsStoreTests-openai-web\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        defaults.set(false, forKey: \"debugDisableKeychainAccess\")\n        let configStore = testConfigStore(suiteName: suite)\n\n        let store = SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        #expect(store.openAIWebAccessEnabled == true)\n        #expect(defaults.bool(forKey: \"openAIWebAccessEnabled\") == true)\n        #expect(store.codexCookieSource == .auto)\n    }\n\n    @Test\n    func `menu observation token updates on defaults change`() async throws {\n        let suite = \"SettingsStoreTests-observation-defaults\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n\n        let store = SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        var didChange = false\n\n        withObservationTracking {\n            _ = store.menuObservationToken\n        } onChange: {\n            Task { @MainActor in\n                didChange = true\n            }\n        }\n\n        store.statusChecksEnabled.toggle()\n        try? await Task.sleep(nanoseconds: 50_000_000)\n\n        #expect(didChange == true)\n    }\n\n    @Test\n    func `config backed settings trigger observation`() async throws {\n        let suite = \"SettingsStoreTests-observation-config\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n\n        let store = SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        var didChange = false\n\n        withObservationTracking {\n            _ = store.codexCookieSource\n        } onChange: {\n            Task { @MainActor in\n                didChange = true\n            }\n        }\n\n        store.codexCookieSource = .manual\n        try? await Task.sleep(nanoseconds: 50_000_000)\n\n        #expect(didChange == true)\n    }\n\n    @Test\n    func `provider order defaults to all cases`() throws {\n        let suite = \"SettingsStoreTests-providerOrder-default\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n\n        let store = SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        #expect(store.orderedProviders() == UsageProvider.allCases)\n    }\n\n    @Test\n    func `provider order persists and appends new providers`() throws {\n        let suite = \"SettingsStoreTests-providerOrder-persist\"\n        let defaultsA = try #require(UserDefaults(suiteName: suite))\n        defaultsA.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n\n        // Partial list to mimic \"older version\" missing providers.\n        let config = CodexBarConfig(providers: [\n            ProviderConfig(id: .gemini),\n            ProviderConfig(id: .codex),\n        ])\n        try configStore.save(config)\n\n        let storeA = SettingsStore(\n            userDefaults: defaultsA,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        #expect(storeA.orderedProviders() == [\n            .gemini,\n            .codex,\n            .claude,\n            .cursor,\n            .opencode,\n            .alibaba,\n            .factory,\n            .antigravity,\n            .copilot,\n            .zai,\n            .minimax,\n            .kimi,\n            .kilo,\n            .kiro,\n            .vertexai,\n            .augment,\n            .jetbrains,\n            .kimik2,\n            .amp,\n            .ollama,\n            .synthetic,\n            .warp,\n            .openrouter,\n        ])\n\n        // Move one provider; ensure it's persisted across instances.\n        let antigravityIndex = try #require(storeA.orderedProviders().firstIndex(of: .antigravity))\n        storeA.moveProvider(fromOffsets: IndexSet(integer: antigravityIndex), toOffset: 0)\n\n        let defaultsB = try #require(UserDefaults(suiteName: suite))\n        let storeB = SettingsStore(\n            userDefaults: defaultsB,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        #expect(storeB.orderedProviders().first == .antigravity)\n    }\n\n    @Test\n    func settingAlibabaAPIKeyEnablesProvider() throws {\n        let suite = \"SettingsStoreTests-alibaba-enable-on-token\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n\n        let store = SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        let metadata = try #require(ProviderDescriptorRegistry.metadata[.alibaba])\n        store.setProviderEnabled(provider: .alibaba, metadata: metadata, enabled: false)\n\n        store.alibabaCodingPlanAPIToken = \"cpk-test-token\"\n\n        #expect(store.isProviderEnabled(provider: .alibaba, metadata: metadata))\n    }\n\n    @Test\n    func alibabaProviderAutoEnablesOnStartupWhenTokenExists() throws {\n        let suite = \"SettingsStoreTests-alibaba-auto-enable-startup\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n\n        let config = CodexBarConfig(providers: [\n            ProviderConfig(id: .alibaba, enabled: false, apiKey: \"cpk-startup-token\"),\n        ])\n        try configStore.save(config)\n\n        let store = SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n\n        let metadata = try #require(ProviderDescriptorRegistry.metadata[.alibaba])\n        #expect(store.isProviderEnabled(provider: .alibaba, metadata: metadata))\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/StatusItemAnimationTests.swift",
    "content": "import AppKit\nimport CodexBarCore\nimport Testing\n@testable import CodexBar\n\n@MainActor\nstruct StatusItemAnimationTests {\n    private func maxAlpha(in rep: NSBitmapImageRep) -> CGFloat {\n        var maxAlpha: CGFloat = 0\n        for x in 0..<rep.pixelsWide {\n            for y in 0..<rep.pixelsHigh {\n                let alpha = (rep.colorAt(x: x, y: y) ?? .clear).alphaComponent\n                if alpha > maxAlpha {\n                    maxAlpha = alpha\n                }\n            }\n        }\n        return maxAlpha\n    }\n\n    private func makeStatusBarForTesting() -> NSStatusBar {\n        let env = ProcessInfo.processInfo.environment\n        if env[\"GITHUB_ACTIONS\"] == \"true\" || env[\"CI\"] == \"true\" {\n            return .system\n        }\n        return NSStatusBar()\n    }\n\n    @Test\n    func `merged icon loading animation tracks selected provider only`() {\n        let settings = SettingsStore(\n            configStore: testConfigStore(suiteName: \"StatusItemAnimationTests-merged\"),\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n        settings.statusChecksEnabled = false\n        settings.refreshFrequency = .manual\n        settings.mergeIcons = true\n        settings.selectedMenuProvider = .codex\n\n        let registry = ProviderRegistry.shared\n        if let codexMeta = registry.metadata[.codex] {\n            settings.setProviderEnabled(provider: .codex, metadata: codexMeta, enabled: true)\n        }\n        if let claudeMeta = registry.metadata[.claude] {\n            settings.setProviderEnabled(provider: .claude, metadata: claudeMeta, enabled: true)\n        }\n        if let geminiMeta = registry.metadata[.gemini] {\n            settings.setProviderEnabled(provider: .gemini, metadata: geminiMeta, enabled: false)\n        }\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n        let controller = StatusItemController(\n            store: store,\n            settings: settings,\n            account: fetcher.loadAccountInfo(),\n            updater: DisabledUpdaterController(),\n            preferencesSelection: PreferencesSelection(),\n            statusBar: self.makeStatusBarForTesting())\n\n        let snapshot = UsageSnapshot(\n            primary: RateWindow(usedPercent: 50, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            secondary: nil,\n            updatedAt: Date())\n\n        store._setSnapshotForTesting(snapshot, provider: .codex)\n        store._setSnapshotForTesting(nil, provider: .claude)\n        store._setErrorForTesting(nil, provider: .codex)\n        store._setErrorForTesting(nil, provider: .claude)\n\n        #expect(controller.needsMenuBarIconAnimation() == false)\n    }\n\n    @Test\n    func `merged icon loading animation does not flip layout when weekly hits zero`() {\n        let settings = SettingsStore(\n            configStore: testConfigStore(suiteName: \"StatusItemAnimationTests-weekly\"),\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n        settings.statusChecksEnabled = false\n        settings.refreshFrequency = .manual\n        settings.mergeIcons = true\n        settings.selectedMenuProvider = .codex\n        settings.menuBarShowsBrandIconWithPercent = false\n\n        let registry = ProviderRegistry.shared\n        if let codexMeta = registry.metadata[.codex] {\n            settings.setProviderEnabled(provider: .codex, metadata: codexMeta, enabled: true)\n        }\n        if let claudeMeta = registry.metadata[.claude] {\n            settings.setProviderEnabled(provider: .claude, metadata: claudeMeta, enabled: true)\n        }\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n\n        // Seed with data so init doesn't start the animation driver.\n        let snapshot = UsageSnapshot(\n            primary: RateWindow(usedPercent: 50, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            secondary: RateWindow(usedPercent: 50, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            updatedAt: Date())\n        store._setSnapshotForTesting(snapshot, provider: .codex)\n\n        let controller = StatusItemController(\n            store: store,\n            settings: settings,\n            account: fetcher.loadAccountInfo(),\n            updater: DisabledUpdaterController(),\n            preferencesSelection: PreferencesSelection(),\n            statusBar: self.makeStatusBarForTesting())\n\n        // Enter loading state: no data, no stale error.\n        store._setSnapshotForTesting(nil, provider: .codex)\n        store._setSnapshotForTesting(nil, provider: .claude)\n        store._setErrorForTesting(nil, provider: .codex)\n        store._setErrorForTesting(nil, provider: .claude)\n\n        controller.animationPattern = .knightRider\n        #expect(controller.needsMenuBarIconAnimation() == true)\n\n        // At phase = π/2, the secondary bar hits 0 (weeklyRemaining == 0) due to a π offset.\n        // Regression: this used to flip IconRenderer into the \"weekly exhausted\" layout and cause toolbar flicker.\n        controller.applyIcon(phase: .pi / 2)\n\n        guard let image = controller.statusItem.button?.image else {\n            #expect(Bool(false))\n            return\n        }\n        let rep = image.representations.compactMap { $0 as? NSBitmapImageRep }.first(where: {\n            $0.pixelsWide == 36 && $0.pixelsHigh == 36\n        })\n        #expect(rep != nil)\n        guard let rep else { return }\n\n        let alpha = (rep.colorAt(x: 18, y: 12) ?? .clear).alphaComponent\n        #expect(alpha > 0.05)\n    }\n\n    @Test\n    func `warp no bonus layout is preserved in show used mode when bonus is exhausted`() {\n        let settings = SettingsStore(\n            configStore: testConfigStore(suiteName: \"StatusItemAnimationTests-warp-no-bonus-used\"),\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n        settings.statusChecksEnabled = false\n        settings.refreshFrequency = .manual\n        settings.mergeIcons = false\n        settings.menuBarShowsBrandIconWithPercent = false\n        settings.usageBarsShowUsed = true\n\n        let registry = ProviderRegistry.shared\n        if let warpMeta = registry.metadata[.warp] {\n            settings.setProviderEnabled(provider: .warp, metadata: warpMeta, enabled: true)\n        }\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n        let controller = StatusItemController(\n            store: store,\n            settings: settings,\n            account: fetcher.loadAccountInfo(),\n            updater: DisabledUpdaterController(),\n            preferencesSelection: PreferencesSelection(),\n            statusBar: self.makeStatusBarForTesting())\n\n        // Primary used=10%. Bonus exhausted: used=100% (remaining=0%).\n        let snapshot = UsageSnapshot(\n            primary: RateWindow(usedPercent: 10, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            secondary: RateWindow(usedPercent: 100, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            updatedAt: Date())\n        store._setSnapshotForTesting(snapshot, provider: .warp)\n        store._setErrorForTesting(nil, provider: .warp)\n\n        controller.applyIcon(for: .warp, phase: nil)\n\n        guard let image = controller.statusItems[.warp]?.button?.image else {\n            #expect(Bool(false))\n            return\n        }\n        let rep = image.representations.compactMap { $0 as? NSBitmapImageRep }.first(where: {\n            $0.pixelsWide == 36 && $0.pixelsHigh == 36\n        })\n        #expect(rep != nil)\n        guard let rep else { return }\n\n        // In the Warp \"no bonus/exhausted bonus\" layout, the bottom bar is a dimmed track.\n        // A pixel near the right side of the bottom bar should remain subdued (not fully opaque).\n        let alpha = (rep.colorAt(x: 25, y: 9) ?? .clear).alphaComponent\n        #expect(alpha < 0.6)\n    }\n\n    @Test\n    func `warp bonus lane is preserved in show used mode when bonus is unused`() {\n        let settings = SettingsStore(\n            configStore: testConfigStore(suiteName: \"StatusItemAnimationTests-warp-unused-bonus-used\"),\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n        settings.statusChecksEnabled = false\n        settings.refreshFrequency = .manual\n        settings.mergeIcons = false\n        settings.menuBarShowsBrandIconWithPercent = false\n        settings.usageBarsShowUsed = true\n\n        let registry = ProviderRegistry.shared\n        if let warpMeta = registry.metadata[.warp] {\n            settings.setProviderEnabled(provider: .warp, metadata: warpMeta, enabled: true)\n        }\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n        let controller = StatusItemController(\n            store: store,\n            settings: settings,\n            account: fetcher.loadAccountInfo(),\n            updater: DisabledUpdaterController(),\n            preferencesSelection: PreferencesSelection(),\n            statusBar: self.makeStatusBarForTesting())\n\n        // Bonus exists but is unused: used=0% (remaining=100%).\n        let snapshot = UsageSnapshot(\n            primary: RateWindow(usedPercent: 10, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            secondary: RateWindow(usedPercent: 0, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            updatedAt: Date())\n        store._setSnapshotForTesting(snapshot, provider: .warp)\n        store._setErrorForTesting(nil, provider: .warp)\n\n        controller.applyIcon(for: .warp, phase: nil)\n\n        guard let image = controller.statusItems[.warp]?.button?.image else {\n            #expect(Bool(false))\n            return\n        }\n        let rep = image.representations.compactMap { $0 as? NSBitmapImageRep }.first(where: {\n            $0.pixelsWide == 36 && $0.pixelsHigh == 36\n        })\n        #expect(rep != nil)\n        guard let rep else { return }\n\n        // When we incorrectly treat \"0 used\" as \"no bonus\", the Warp branch makes the top bar full (100%).\n        // A pixel near the right side of the top bar should remain in the track-only range for 10% usage.\n        let alpha = (rep.colorAt(x: 31, y: 25) ?? .clear).alphaComponent\n        #expect(alpha < 0.6)\n    }\n\n    @Test\n    func `menu bar percent uses configured metric`() {\n        let settings = SettingsStore(\n            configStore: testConfigStore(suiteName: \"StatusItemAnimationTests-metric\"),\n            zaiTokenStore: NoopZaiTokenStore())\n        settings.statusChecksEnabled = false\n        settings.refreshFrequency = .manual\n        settings.mergeIcons = true\n        settings.selectedMenuProvider = .codex\n        settings.setMenuBarMetricPreference(.secondary, for: .codex)\n\n        let registry = ProviderRegistry.shared\n        if let codexMeta = registry.metadata[.codex] {\n            settings.setProviderEnabled(provider: .codex, metadata: codexMeta, enabled: true)\n        }\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n        let controller = StatusItemController(\n            store: store,\n            settings: settings,\n            account: fetcher.loadAccountInfo(),\n            updater: DisabledUpdaterController(),\n            preferencesSelection: PreferencesSelection(),\n            statusBar: self.makeStatusBarForTesting())\n\n        let snapshot = UsageSnapshot(\n            primary: RateWindow(usedPercent: 12, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            secondary: RateWindow(usedPercent: 42, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            updatedAt: Date())\n\n        store._setSnapshotForTesting(snapshot, provider: .codex)\n        store._setErrorForTesting(nil, provider: .codex)\n\n        let window = controller.menuBarMetricWindow(for: .codex, snapshot: snapshot)\n\n        #expect(window?.usedPercent == 42)\n    }\n\n    @Test\n    func `menu bar percent automatic prefers rate limit for kimi`() {\n        let settings = SettingsStore(\n            configStore: testConfigStore(suiteName: \"StatusItemAnimationTests-kimi-automatic\"),\n            zaiTokenStore: NoopZaiTokenStore())\n        settings.statusChecksEnabled = false\n        settings.refreshFrequency = .manual\n        settings.mergeIcons = true\n        settings.selectedMenuProvider = .kimi\n        settings.setMenuBarMetricPreference(.automatic, for: .kimi)\n\n        let registry = ProviderRegistry.shared\n        if let kimiMeta = registry.metadata[.kimi] {\n            settings.setProviderEnabled(provider: .kimi, metadata: kimiMeta, enabled: true)\n        }\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n        let controller = StatusItemController(\n            store: store,\n            settings: settings,\n            account: fetcher.loadAccountInfo(),\n            updater: DisabledUpdaterController(),\n            preferencesSelection: PreferencesSelection(),\n            statusBar: self.makeStatusBarForTesting())\n\n        let snapshot = UsageSnapshot(\n            primary: RateWindow(usedPercent: 12, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            secondary: RateWindow(usedPercent: 42, windowMinutes: 300, resetsAt: nil, resetDescription: nil),\n            updatedAt: Date())\n\n        store._setSnapshotForTesting(snapshot, provider: .kimi)\n        store._setErrorForTesting(nil, provider: .kimi)\n\n        let window = controller.menuBarMetricWindow(for: .kimi, snapshot: snapshot)\n\n        #expect(window?.usedPercent == 42)\n    }\n\n    @Test\n    func `menu bar percent uses average for gemini`() {\n        let settings = SettingsStore(\n            configStore: testConfigStore(suiteName: \"StatusItemAnimationTests-average\"),\n            zaiTokenStore: NoopZaiTokenStore())\n        settings.statusChecksEnabled = false\n        settings.refreshFrequency = .manual\n        settings.mergeIcons = true\n        settings.selectedMenuProvider = .gemini\n        settings.setMenuBarMetricPreference(.average, for: .gemini)\n\n        let registry = ProviderRegistry.shared\n        if let geminiMeta = registry.metadata[.gemini] {\n            settings.setProviderEnabled(provider: .gemini, metadata: geminiMeta, enabled: true)\n        }\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n        let controller = StatusItemController(\n            store: store,\n            settings: settings,\n            account: fetcher.loadAccountInfo(),\n            updater: DisabledUpdaterController(),\n            preferencesSelection: PreferencesSelection(),\n            statusBar: self.makeStatusBarForTesting())\n\n        let snapshot = UsageSnapshot(\n            primary: RateWindow(usedPercent: 20, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            secondary: RateWindow(usedPercent: 60, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            updatedAt: Date())\n\n        store._setSnapshotForTesting(snapshot, provider: .gemini)\n        store._setErrorForTesting(nil, provider: .gemini)\n\n        let window = controller.menuBarMetricWindow(for: .gemini, snapshot: snapshot)\n\n        #expect(window?.usedPercent == 40)\n    }\n\n    @Test\n    func `menu bar display text formats percent and pace`() {\n        let now = Date(timeIntervalSince1970: 0)\n        let percentWindow = RateWindow(usedPercent: 40, windowMinutes: nil, resetsAt: nil, resetDescription: nil)\n        let paceWindow = RateWindow(\n            usedPercent: 30,\n            windowMinutes: 10080,\n            resetsAt: now.addingTimeInterval(60 * 60 * 24 * 6),\n            resetDescription: nil)\n        let paceValue = UsagePace.weekly(window: paceWindow, now: now, defaultWindowMinutes: 10080)\n\n        let percent = MenuBarDisplayText.displayText(\n            mode: .percent,\n            percentWindow: percentWindow,\n            pace: paceValue,\n            showUsed: true)\n        let pace = MenuBarDisplayText.displayText(\n            mode: .pace,\n            percentWindow: percentWindow,\n            pace: paceValue,\n            showUsed: true)\n        let both = MenuBarDisplayText.displayText(\n            mode: .both,\n            percentWindow: percentWindow,\n            pace: paceValue,\n            showUsed: true)\n\n        #expect(percent == \"40%\")\n        #expect(pace == \"+16%\")\n        #expect(both == \"40% · +16%\")\n    }\n\n    @Test\n    func `menu bar display text hides when pace unavailable`() {\n        let percentWindow = RateWindow(usedPercent: 40, windowMinutes: nil, resetsAt: nil, resetDescription: nil)\n\n        let pace = MenuBarDisplayText.displayText(\n            mode: .pace,\n            percentWindow: percentWindow,\n            showUsed: true)\n        let both = MenuBarDisplayText.displayText(\n            mode: .both,\n            percentWindow: percentWindow,\n            showUsed: true)\n\n        #expect(pace == nil)\n        #expect(both == nil)\n    }\n\n    @Test\n    func `menu bar display text requires provided pace for codex`() {\n        let percentWindow = RateWindow(usedPercent: 40, windowMinutes: nil, resetsAt: nil, resetDescription: nil)\n\n        let pace = MenuBarDisplayText.displayText(\n            mode: .pace,\n            percentWindow: percentWindow,\n            pace: nil,\n            showUsed: true)\n        let both = MenuBarDisplayText.displayText(\n            mode: .both,\n            percentWindow: percentWindow,\n            pace: nil,\n            showUsed: true)\n\n        #expect(pace == nil)\n        #expect(both == nil)\n    }\n\n    @Test\n    func `menu bar display text uses credits when codex weekly is exhausted`() {\n        let settings = SettingsStore(\n            configStore: testConfigStore(suiteName: \"StatusItemAnimationTests-credits-fallback\"),\n            zaiTokenStore: NoopZaiTokenStore())\n        settings.statusChecksEnabled = false\n        settings.refreshFrequency = .manual\n        settings.mergeIcons = true\n        settings.selectedMenuProvider = .codex\n        settings.menuBarDisplayMode = .percent\n        settings.usageBarsShowUsed = false\n        settings.setMenuBarMetricPreference(.secondary, for: .codex)\n\n        let registry = ProviderRegistry.shared\n        if let codexMeta = registry.metadata[.codex] {\n            settings.setProviderEnabled(provider: .codex, metadata: codexMeta, enabled: true)\n        }\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n        let controller = StatusItemController(\n            store: store,\n            settings: settings,\n            account: fetcher.loadAccountInfo(),\n            updater: DisabledUpdaterController(),\n            preferencesSelection: PreferencesSelection(),\n            statusBar: self.makeStatusBarForTesting())\n\n        let snapshot = UsageSnapshot(\n            primary: RateWindow(usedPercent: 20, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            secondary: RateWindow(usedPercent: 100, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            updatedAt: Date())\n\n        let remainingCredits = (snapshot.primary?.usedPercent ?? 0) * 4.5 + (snapshot.secondary?.usedPercent ?? 0) / 10\n        store._setSnapshotForTesting(snapshot, provider: .codex)\n        store._setErrorForTesting(nil, provider: .codex)\n        store.credits = CreditsSnapshot(remaining: remainingCredits, events: [], updatedAt: Date())\n\n        let displayText = controller.menuBarDisplayText(for: .codex, snapshot: snapshot)\n        let expected = UsageFormatter\n            .creditsString(from: remainingCredits)\n            .replacingOccurrences(of: \" left\", with: \"\")\n\n        #expect(displayText == expected)\n    }\n\n    @Test\n    func `menu bar display text uses credits when codex session is exhausted`() {\n        let settings = SettingsStore(\n            configStore: testConfigStore(suiteName: \"StatusItemAnimationTests-credits-fallback-session\"),\n            zaiTokenStore: NoopZaiTokenStore())\n        settings.statusChecksEnabled = false\n        settings.refreshFrequency = .manual\n        settings.mergeIcons = true\n        settings.selectedMenuProvider = .codex\n        settings.menuBarDisplayMode = .percent\n        settings.usageBarsShowUsed = false\n        settings.setMenuBarMetricPreference(.primary, for: .codex)\n\n        let registry = ProviderRegistry.shared\n        if let codexMeta = registry.metadata[.codex] {\n            settings.setProviderEnabled(provider: .codex, metadata: codexMeta, enabled: true)\n        }\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n        let controller = StatusItemController(\n            store: store,\n            settings: settings,\n            account: fetcher.loadAccountInfo(),\n            updater: DisabledUpdaterController(),\n            preferencesSelection: PreferencesSelection(),\n            statusBar: self.makeStatusBarForTesting())\n\n        let snapshot = UsageSnapshot(\n            primary: RateWindow(usedPercent: 100, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            secondary: RateWindow(usedPercent: 40, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            updatedAt: Date())\n\n        let remainingCredits = (snapshot.primary?.usedPercent ?? 0) - (snapshot.secondary?.usedPercent ?? 0) / 2\n        store._setSnapshotForTesting(snapshot, provider: .codex)\n        store._setErrorForTesting(nil, provider: .codex)\n        store.credits = CreditsSnapshot(remaining: remainingCredits, events: [], updatedAt: Date())\n\n        let displayText = controller.menuBarDisplayText(for: .codex, snapshot: snapshot)\n        let expected = UsageFormatter\n            .creditsString(from: remainingCredits)\n            .replacingOccurrences(of: \" left\", with: \"\")\n\n        #expect(displayText == expected)\n    }\n\n    @Test\n    func `menu bar display text shows zero percent for kilo zero total edge`() {\n        let settings = SettingsStore(\n            configStore: testConfigStore(suiteName: \"StatusItemAnimationTests-kilo-zero-edge\"),\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n        settings.statusChecksEnabled = false\n        settings.refreshFrequency = .manual\n        settings.mergeIcons = true\n        settings.selectedMenuProvider = .kilo\n        settings.menuBarDisplayMode = .percent\n        settings.usageBarsShowUsed = false\n        settings.setMenuBarMetricPreference(.primary, for: .kilo)\n\n        let registry = ProviderRegistry.shared\n        if let kiloMeta = registry.metadata[.kilo] {\n            settings.setProviderEnabled(provider: .kilo, metadata: kiloMeta, enabled: true)\n        }\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n        let controller = StatusItemController(\n            store: store,\n            settings: settings,\n            account: fetcher.loadAccountInfo(),\n            updater: DisabledUpdaterController(),\n            preferencesSelection: PreferencesSelection(),\n            statusBar: self.makeStatusBarForTesting())\n\n        let snapshot = KiloUsageSnapshot(\n            creditsUsed: 0,\n            creditsTotal: 0,\n            creditsRemaining: 0,\n            planName: \"Kilo Pass Pro\",\n            autoTopUpEnabled: true,\n            autoTopUpMethod: \"visa\",\n            updatedAt: Date()).toUsageSnapshot()\n\n        store._setSnapshotForTesting(snapshot, provider: .kilo)\n        store._setErrorForTesting(nil, provider: .kilo)\n\n        let displayText = controller.menuBarDisplayText(for: .kilo, snapshot: snapshot)\n\n        #expect(displayText == \"0%\")\n    }\n\n    @Test\n    func `brand image with status overlay returns original image when no issue`() {\n        let brand = NSImage(size: NSSize(width: 16, height: 16))\n        brand.isTemplate = true\n\n        let output = StatusItemController.brandImageWithStatusOverlay(brand: brand, statusIndicator: .none)\n\n        #expect(output === brand)\n    }\n\n    @Test\n    func `brand image with status overlay draws issue mark`() throws {\n        let size = NSSize(width: 16, height: 16)\n        let brand = NSImage(size: size)\n        brand.lockFocus()\n        NSColor.clear.setFill()\n        NSBezierPath(rect: NSRect(origin: .zero, size: size)).fill()\n        brand.unlockFocus()\n        brand.isTemplate = true\n\n        let baselineData = try #require(brand.tiffRepresentation)\n        let baselineRep = try #require(NSBitmapImageRep(data: baselineData))\n        let baselineAlpha = self.maxAlpha(in: baselineRep)\n\n        let output = StatusItemController.brandImageWithStatusOverlay(brand: brand, statusIndicator: .major)\n\n        #expect(output !== brand)\n        let outputData = try #require(output.tiffRepresentation)\n        let outputRep = try #require(NSBitmapImageRep(data: outputData))\n        let outputAlpha = self.maxAlpha(in: outputRep)\n        #expect(baselineAlpha < 0.01)\n        #expect(outputAlpha > 0.01)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/StatusItemControllerMenuTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n@testable import CodexBar\n\nstruct StatusItemControllerMenuTests {\n    private func makeSnapshot(primary: RateWindow?, secondary: RateWindow?) -> UsageSnapshot {\n        UsageSnapshot(primary: primary, secondary: secondary, updatedAt: Date())\n    }\n\n    @Test\n    func `cursor switcher falls back to secondary when plan exhausted and showing remaining`() {\n        let primary = RateWindow(usedPercent: 100, windowMinutes: nil, resetsAt: nil, resetDescription: nil)\n        let secondary = RateWindow(usedPercent: 36, windowMinutes: nil, resetsAt: nil, resetDescription: nil)\n        let snapshot = self.makeSnapshot(primary: primary, secondary: secondary)\n\n        let percent = StatusItemController.switcherWeeklyMetricPercent(\n            for: .cursor,\n            snapshot: snapshot,\n            showUsed: false)\n\n        #expect(percent == 64)\n    }\n\n    @Test\n    func `cursor switcher uses primary when showing used`() {\n        let primary = RateWindow(usedPercent: 100, windowMinutes: nil, resetsAt: nil, resetDescription: nil)\n        let secondary = RateWindow(usedPercent: 36, windowMinutes: nil, resetsAt: nil, resetDescription: nil)\n        let snapshot = self.makeSnapshot(primary: primary, secondary: secondary)\n\n        let percent = StatusItemController.switcherWeeklyMetricPercent(\n            for: .cursor,\n            snapshot: snapshot,\n            showUsed: true)\n\n        #expect(percent == 100)\n    }\n\n    @Test\n    func `cursor switcher keeps primary when remaining is positive`() {\n        let primary = RateWindow(usedPercent: 20, windowMinutes: nil, resetsAt: nil, resetDescription: nil)\n        let secondary = RateWindow(usedPercent: 40, windowMinutes: nil, resetsAt: nil, resetDescription: nil)\n        let snapshot = self.makeSnapshot(primary: primary, secondary: secondary)\n\n        let percent = StatusItemController.switcherWeeklyMetricPercent(\n            for: .cursor,\n            snapshot: snapshot,\n            showUsed: false)\n\n        #expect(percent == 80)\n    }\n\n    @Test\n    func `open router brand fallback enabled when no key limit configured`() {\n        let snapshot = OpenRouterUsageSnapshot(\n            totalCredits: 50,\n            totalUsage: 45,\n            balance: 5,\n            usedPercent: 90,\n            keyDataFetched: true,\n            keyLimit: nil,\n            keyUsage: nil,\n            rateLimit: nil,\n            updatedAt: Date()).toUsageSnapshot()\n\n        #expect(StatusItemController.shouldUseOpenRouterBrandFallback(\n            provider: .openrouter,\n            snapshot: snapshot))\n        #expect(MenuBarDisplayText.percentText(window: snapshot.primary, showUsed: false) == nil)\n    }\n\n    @Test\n    func `open router brand fallback disabled when key quota fetch unavailable`() {\n        let snapshot = OpenRouterUsageSnapshot(\n            totalCredits: 50,\n            totalUsage: 45,\n            balance: 5,\n            usedPercent: 90,\n            keyDataFetched: false,\n            keyLimit: nil,\n            keyUsage: nil,\n            rateLimit: nil,\n            updatedAt: Date()).toUsageSnapshot()\n\n        #expect(!StatusItemController.shouldUseOpenRouterBrandFallback(\n            provider: .openrouter,\n            snapshot: snapshot))\n    }\n\n    @Test\n    func `open router brand fallback disabled when key quota available`() {\n        let snapshot = OpenRouterUsageSnapshot(\n            totalCredits: 50,\n            totalUsage: 45,\n            balance: 5,\n            usedPercent: 90,\n            keyLimit: 20,\n            keyUsage: 2,\n            rateLimit: nil,\n            updatedAt: Date()).toUsageSnapshot()\n\n        #expect(!StatusItemController.shouldUseOpenRouterBrandFallback(\n            provider: .openrouter,\n            snapshot: snapshot))\n        #expect(snapshot.primary?.usedPercent == 10)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/StatusMenuTests.swift",
    "content": "import AppKit\nimport CodexBarCore\nimport Testing\n@testable import CodexBar\n\n@MainActor\nstruct StatusMenuTests {\n    private func disableMenuCardsForTesting() {\n        StatusItemController.menuCardRenderingEnabled = false\n        StatusItemController.menuRefreshEnabled = false\n    }\n\n    private func makeStatusBarForTesting() -> NSStatusBar {\n        let env = ProcessInfo.processInfo.environment\n        if env[\"GITHUB_ACTIONS\"] == \"true\" || env[\"CI\"] == \"true\" {\n            return .system\n        }\n        return NSStatusBar()\n    }\n\n    private func makeSettings() -> SettingsStore {\n        let suite = \"StatusMenuTests-\\(UUID().uuidString)\"\n        let defaults = UserDefaults(suiteName: suite)!\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n        return SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n    }\n\n    private func switcherButtons(in menu: NSMenu) -> [NSButton] {\n        guard let switcherView = menu.items.first?.view as? ProviderSwitcherView else { return [] }\n        return switcherView.subviews\n            .compactMap { $0 as? NSButton }\n            .sorted { $0.tag < $1.tag }\n    }\n\n    private func representedIDs(in menu: NSMenu) -> [String] {\n        menu.items.compactMap { $0.representedObject as? String }\n    }\n\n    @Test\n    func `alibaba dashboard action follows selected region`() {\n        self.disableMenuCardsForTesting()\n        let settings = self.makeSettings()\n        settings.statusChecksEnabled = false\n        settings.refreshFrequency = .manual\n        settings.mergeIcons = false\n        settings.alibabaCodingPlanAPIRegion = .chinaMainland\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n        let controller = StatusItemController(\n            store: store,\n            settings: settings,\n            account: fetcher.loadAccountInfo(),\n            updater: DisabledUpdaterController(),\n            preferencesSelection: PreferencesSelection(),\n            statusBar: self.makeStatusBarForTesting())\n\n        #expect(controller.dashboardURL(for: .alibaba) == AlibabaCodingPlanAPIRegion.chinaMainland.dashboardURL)\n    }\n\n    @Test\n    func `remembers provider when menu opens`() {\n        self.disableMenuCardsForTesting()\n        let settings = self.makeSettings()\n        settings.statusChecksEnabled = false\n        settings.refreshFrequency = .manual\n        settings.mergeIcons = true\n\n        let registry = ProviderRegistry.shared\n        if let codexMeta = registry.metadata[.codex] {\n            settings.setProviderEnabled(provider: .codex, metadata: codexMeta, enabled: false)\n        }\n        if let claudeMeta = registry.metadata[.claude] {\n            settings.setProviderEnabled(provider: .claude, metadata: claudeMeta, enabled: true)\n        }\n        if let geminiMeta = registry.metadata[.gemini] {\n            settings.setProviderEnabled(provider: .gemini, metadata: geminiMeta, enabled: false)\n        }\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n        let controller = StatusItemController(\n            store: store,\n            settings: settings,\n            account: fetcher.loadAccountInfo(),\n            updater: DisabledUpdaterController(),\n            preferencesSelection: PreferencesSelection(),\n            statusBar: self.makeStatusBarForTesting())\n\n        let claudeMenu = controller.makeMenu()\n        controller.menuWillOpen(claudeMenu)\n        #expect(controller.lastMenuProvider == .claude)\n\n        // No providers enabled: fall back to Codex.\n        for provider in UsageProvider.allCases {\n            if let meta = registry.metadata[provider] {\n                settings.setProviderEnabled(provider: provider, metadata: meta, enabled: false)\n            }\n        }\n        let unmappedMenu = controller.makeMenu()\n        controller.menuWillOpen(unmappedMenu)\n        #expect(controller.lastMenuProvider == .codex)\n    }\n\n    @Test\n    func `merged menu open does not persist resolved provider when selection is nil`() {\n        self.disableMenuCardsForTesting()\n        let settings = self.makeSettings()\n        settings.statusChecksEnabled = false\n        settings.refreshFrequency = .manual\n        settings.mergeIcons = true\n        settings.selectedMenuProvider = nil\n\n        let registry = ProviderRegistry.shared\n        var enabledProviders: [UsageProvider] = []\n        for provider in UsageProvider.allCases {\n            guard let metadata = registry.metadata[provider] else { continue }\n            let shouldEnable = enabledProviders.count < 2\n            settings.setProviderEnabled(provider: provider, metadata: metadata, enabled: shouldEnable)\n            if shouldEnable {\n                enabledProviders.append(provider)\n            }\n        }\n        #expect(enabledProviders.count == 2)\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n        let controller = StatusItemController(\n            store: store,\n            settings: settings,\n            account: fetcher.loadAccountInfo(),\n            updater: DisabledUpdaterController(),\n            preferencesSelection: PreferencesSelection(),\n            statusBar: self.makeStatusBarForTesting())\n\n        let expectedResolved = store.enabledProviders().first ?? .codex\n        #expect(store.enabledProviders().count > 1)\n        #expect(controller.shouldMergeIcons == true)\n        let menu = controller.makeMenu()\n        #expect(settings.selectedMenuProvider == nil)\n        controller.menuWillOpen(menu)\n        #expect(settings.selectedMenuProvider == nil)\n        #expect(controller.lastMenuProvider == expectedResolved)\n    }\n\n    @Test\n    func `merged menu refresh uses resolved enabled provider when persisted selection is disabled`() {\n        self.disableMenuCardsForTesting()\n        let settings = self.makeSettings()\n        settings.statusChecksEnabled = false\n        settings.refreshFrequency = .manual\n        settings.mergeIcons = true\n        settings.selectedMenuProvider = .codex\n\n        let registry = ProviderRegistry.shared\n        if let codexMeta = registry.metadata[.codex] {\n            settings.setProviderEnabled(provider: .codex, metadata: codexMeta, enabled: false)\n        }\n        if let claudeMeta = registry.metadata[.claude] {\n            settings.setProviderEnabled(provider: .claude, metadata: claudeMeta, enabled: true)\n        }\n        if let geminiMeta = registry.metadata[.gemini] {\n            settings.setProviderEnabled(provider: .gemini, metadata: geminiMeta, enabled: true)\n        }\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n        let event = CreditEvent(date: Date(), service: \"CLI\", creditsUsed: 1)\n        let breakdown = OpenAIDashboardSnapshot.makeDailyBreakdown(from: [event], maxDays: 30)\n        store.openAIDashboard = OpenAIDashboardSnapshot(\n            signedInEmail: \"user@example.com\",\n            codeReviewRemainingPercent: 100,\n            creditEvents: [event],\n            dailyBreakdown: breakdown,\n            usageBreakdown: breakdown,\n            creditsPurchaseURL: nil,\n            updatedAt: Date())\n        let controller = StatusItemController(\n            store: store,\n            settings: settings,\n            account: fetcher.loadAccountInfo(),\n            updater: DisabledUpdaterController(),\n            preferencesSelection: PreferencesSelection(),\n            statusBar: self.makeStatusBarForTesting())\n\n        let expectedResolved = store.enabledProviders().first ?? .codex\n        #expect(store.enabledProviders().count > 1)\n        #expect(controller.shouldMergeIcons == true)\n\n        func hasOpenAIWebSubmenus(_ menu: NSMenu) -> Bool {\n            let usageItem = menu.items.first { ($0.representedObject as? String) == \"menuCardUsage\" }\n            let creditsItem = menu.items.first { ($0.representedObject as? String) == \"menuCardCredits\" }\n            let hasUsageBreakdown = usageItem?.submenu?.items\n                .contains { ($0.representedObject as? String) == \"usageBreakdownChart\" } == true\n            let hasCreditsHistory = creditsItem?.submenu?.items\n                .contains { ($0.representedObject as? String) == \"creditsHistoryChart\" } == true\n            return hasUsageBreakdown || hasCreditsHistory\n        }\n\n        let menu = controller.makeMenu()\n        controller.menuWillOpen(menu)\n\n        #expect(controller.lastMenuProvider == expectedResolved)\n        #expect(settings.selectedMenuProvider == .codex)\n        #expect(hasOpenAIWebSubmenus(menu) == false)\n\n        controller.menuContentVersion &+= 1\n        controller.refreshOpenMenusIfNeeded()\n\n        #expect(hasOpenAIWebSubmenus(menu) == false)\n    }\n\n    @Test\n    func `open merged menu rebuilds switcher when usage bars mode changes`() {\n        self.disableMenuCardsForTesting()\n        let settings = self.makeSettings()\n        settings.statusChecksEnabled = false\n        settings.refreshFrequency = .manual\n        settings.mergeIcons = true\n        settings.selectedMenuProvider = .codex\n        settings.usageBarsShowUsed = false\n\n        let registry = ProviderRegistry.shared\n        for provider in UsageProvider.allCases {\n            guard let metadata = registry.metadata[provider] else { continue }\n            let shouldEnable = provider == .codex || provider == .claude\n            settings.setProviderEnabled(provider: provider, metadata: metadata, enabled: shouldEnable)\n        }\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n        let controller = StatusItemController(\n            store: store,\n            settings: settings,\n            account: fetcher.loadAccountInfo(),\n            updater: DisabledUpdaterController(),\n            preferencesSelection: PreferencesSelection(),\n            statusBar: self.makeStatusBarForTesting())\n\n        #expect(store.enabledProviders().count == 2)\n        #expect(controller.shouldMergeIcons == true)\n\n        let menu = controller.makeMenu()\n        controller.menuWillOpen(menu)\n\n        let initialSwitcher = menu.items.first?.view as? ProviderSwitcherView\n        #expect(initialSwitcher != nil)\n        let initialSwitcherID = initialSwitcher.map(ObjectIdentifier.init)\n\n        settings.usageBarsShowUsed = true\n        controller.handleProviderConfigChange(reason: \"usageBarsShowUsed\")\n\n        let updatedSwitcher = menu.items.first?.view as? ProviderSwitcherView\n        #expect(updatedSwitcher != nil)\n        if let initialSwitcherID, let updatedSwitcher {\n            #expect(initialSwitcherID != ObjectIdentifier(updatedSwitcher))\n        }\n    }\n\n    @Test\n    func `merged switcher includes overview tab when multiple providers enabled`() {\n        self.disableMenuCardsForTesting()\n        let settings = self.makeSettings()\n        settings.statusChecksEnabled = false\n        settings.refreshFrequency = .manual\n        settings.mergeIcons = true\n        settings.selectedMenuProvider = .claude\n        settings.mergedMenuLastSelectedWasOverview = false\n\n        let registry = ProviderRegistry.shared\n        for provider in UsageProvider.allCases {\n            guard let metadata = registry.metadata[provider] else { continue }\n            let shouldEnable = provider == .codex || provider == .claude\n            settings.setProviderEnabled(provider: provider, metadata: metadata, enabled: shouldEnable)\n        }\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n        let controller = StatusItemController(\n            store: store,\n            settings: settings,\n            account: fetcher.loadAccountInfo(),\n            updater: DisabledUpdaterController(),\n            preferencesSelection: PreferencesSelection(),\n            statusBar: self.makeStatusBarForTesting())\n\n        let menu = controller.makeMenu()\n        controller.menuWillOpen(menu)\n\n        let buttons = self.switcherButtons(in: menu)\n        #expect(buttons.count == store.enabledProvidersForDisplay().count + 1)\n        #expect(buttons.contains(where: { $0.tag == 0 }))\n        #expect(buttons.first(where: { $0.state == .on })?.tag == 2)\n    }\n\n    @Test\n    func `merged switcher overview selection persists without overwriting provider selection`() {\n        self.disableMenuCardsForTesting()\n        let settings = self.makeSettings()\n        settings.statusChecksEnabled = false\n        settings.refreshFrequency = .manual\n        settings.mergeIcons = true\n        settings.selectedMenuProvider = .claude\n        settings.mergedMenuLastSelectedWasOverview = false\n\n        let registry = ProviderRegistry.shared\n        for provider in UsageProvider.allCases {\n            guard let metadata = registry.metadata[provider] else { continue }\n            let shouldEnable = provider == .codex || provider == .claude\n            settings.setProviderEnabled(provider: provider, metadata: metadata, enabled: shouldEnable)\n        }\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n        let controller = StatusItemController(\n            store: store,\n            settings: settings,\n            account: fetcher.loadAccountInfo(),\n            updater: DisabledUpdaterController(),\n            preferencesSelection: PreferencesSelection(),\n            statusBar: self.makeStatusBarForTesting())\n\n        let menu = controller.makeMenu()\n        controller.menuWillOpen(menu)\n        let overviewButton = self.switcherButtons(in: menu).first(where: { $0.tag == 0 })\n        #expect(overviewButton != nil)\n        overviewButton?.performClick(nil)\n\n        #expect(settings.mergedMenuLastSelectedWasOverview == true)\n        #expect(settings.selectedMenuProvider == .claude)\n\n        controller.menuDidClose(menu)\n\n        let reopenedMenu = controller.makeMenu()\n        controller.menuWillOpen(reopenedMenu)\n        let reopenedSelectedTag = self.switcherButtons(in: reopenedMenu).first(where: { $0.state == .on })?.tag\n        #expect(reopenedSelectedTag == 0)\n        #expect(settings.selectedMenuProvider == .claude)\n    }\n\n    @Test\n    func `open menu rebuilds switcher when overview availability changes`() {\n        self.disableMenuCardsForTesting()\n        let settings = self.makeSettings()\n        settings.statusChecksEnabled = false\n        settings.refreshFrequency = .manual\n        settings.mergeIcons = true\n        settings.selectedMenuProvider = .codex\n        settings.mergedMenuLastSelectedWasOverview = false\n\n        let registry = ProviderRegistry.shared\n        for provider in UsageProvider.allCases {\n            guard let metadata = registry.metadata[provider] else { continue }\n            let shouldEnable = provider == .codex || provider == .claude\n            settings.setProviderEnabled(provider: provider, metadata: metadata, enabled: shouldEnable)\n        }\n\n        let activeProviders: [UsageProvider] = [.codex, .claude]\n        _ = settings.setMergedOverviewProviderSelection(\n            provider: .codex,\n            isSelected: false,\n            activeProviders: activeProviders)\n        _ = settings.setMergedOverviewProviderSelection(\n            provider: .claude,\n            isSelected: false,\n            activeProviders: activeProviders)\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n        let controller = StatusItemController(\n            store: store,\n            settings: settings,\n            account: fetcher.loadAccountInfo(),\n            updater: DisabledUpdaterController(),\n            preferencesSelection: PreferencesSelection(),\n            statusBar: self.makeStatusBarForTesting())\n\n        let menu = controller.makeMenu()\n        controller.menuWillOpen(menu)\n\n        let initialButtons = self.switcherButtons(in: menu)\n        #expect(initialButtons.count == activeProviders.count)\n\n        _ = settings.setMergedOverviewProviderSelection(\n            provider: .codex,\n            isSelected: true,\n            activeProviders: activeProviders)\n        controller.menuContentVersion &+= 1\n        controller.refreshOpenMenusIfNeeded()\n\n        let updatedButtons = self.switcherButtons(in: menu)\n        #expect(updatedButtons.count == activeProviders.count + 1)\n    }\n\n    @Test\n    func `overview tab omits contextual provider actions`() {\n        self.disableMenuCardsForTesting()\n        let settings = self.makeSettings()\n        settings.statusChecksEnabled = false\n        settings.refreshFrequency = .manual\n        settings.mergeIcons = true\n        settings.selectedMenuProvider = .codex\n        settings.mergedMenuLastSelectedWasOverview = true\n\n        let registry = ProviderRegistry.shared\n        for provider in UsageProvider.allCases {\n            guard let metadata = registry.metadata[provider] else { continue }\n            let shouldEnable = provider == .codex || provider == .claude || provider == .cursor\n            settings.setProviderEnabled(provider: provider, metadata: metadata, enabled: shouldEnable)\n        }\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n        let controller = StatusItemController(\n            store: store,\n            settings: settings,\n            account: fetcher.loadAccountInfo(),\n            updater: DisabledUpdaterController(),\n            preferencesSelection: PreferencesSelection(),\n            statusBar: self.makeStatusBarForTesting())\n\n        let menu = controller.makeMenu()\n        controller.menuWillOpen(menu)\n\n        let titles = Set(menu.items.map(\\.title))\n        #expect(!titles.contains(\"Add Account...\"))\n        #expect(!titles.contains(\"Switch Account...\"))\n        #expect(!titles.contains(\"Usage Dashboard\"))\n        #expect(!titles.contains(\"Status Page\"))\n        #expect(titles.contains(\"Settings...\"))\n        #expect(titles.contains(\"About CodexBar\"))\n        #expect(titles.contains(\"Quit\"))\n    }\n\n    @Test\n    func `status blurb uses wrapped view-backed menu item`() {\n        self.disableMenuCardsForTesting()\n        let settings = self.makeSettings()\n        settings.statusChecksEnabled = true\n        settings.refreshFrequency = .manual\n        settings.mergeIcons = false\n\n        let registry = ProviderRegistry.shared\n        for provider in UsageProvider.allCases {\n            guard let metadata = registry.metadata[provider] else { continue }\n            settings.setProviderEnabled(provider: provider, metadata: metadata, enabled: provider == .codex)\n        }\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n        let statusText = \"An SSL error has occurred and a secure connection to the server cannot be made.\"\n        store.statuses[.codex] = ProviderStatus(\n            indicator: .critical,\n            description: statusText,\n            updatedAt: nil)\n\n        let controller = StatusItemController(\n            store: store,\n            settings: settings,\n            account: fetcher.loadAccountInfo(),\n            updater: DisabledUpdaterController(),\n            preferencesSelection: PreferencesSelection(),\n            statusBar: self.makeStatusBarForTesting())\n\n        let menu = controller.makeMenu(for: .codex)\n        controller.menuWillOpen(menu)\n\n        let statusItem = menu.items.first(where: { $0.toolTip == statusText })\n        #expect(statusItem != nil)\n        #expect(statusItem?.view != nil)\n        #expect(statusItem?.title == statusText)\n        #expect(statusItem?.view?.frame.width == 310)\n    }\n\n    @Test\n    func `provider toggle updates status item visibility`() {\n        self.disableMenuCardsForTesting()\n        let settings = self.makeSettings()\n        settings.statusChecksEnabled = false\n        settings.refreshFrequency = .manual\n        settings.mergeIcons = false\n        settings.providerDetectionCompleted = true\n\n        let registry = ProviderRegistry.shared\n        if let codexMeta = registry.metadata[.codex] {\n            settings.setProviderEnabled(provider: .codex, metadata: codexMeta, enabled: true)\n        }\n        if let claudeMeta = registry.metadata[.claude] {\n            settings.setProviderEnabled(provider: .claude, metadata: claudeMeta, enabled: true)\n        }\n        if let geminiMeta = registry.metadata[.gemini] {\n            settings.setProviderEnabled(provider: .gemini, metadata: geminiMeta, enabled: false)\n        }\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n        let controller = StatusItemController(\n            store: store,\n            settings: settings,\n            account: fetcher.loadAccountInfo(),\n            updater: DisabledUpdaterController(),\n            preferencesSelection: PreferencesSelection(),\n            statusBar: self.makeStatusBarForTesting())\n\n        #expect(controller.statusItems[.claude]?.isVisible == true)\n\n        if let claudeMeta = registry.metadata[.claude] {\n            settings.setProviderEnabled(provider: .claude, metadata: claudeMeta, enabled: false)\n        }\n        controller.handleProviderConfigChange(reason: \"test\")\n        #expect(controller.statusItems[.claude]?.isVisible == false)\n    }\n\n    @Test\n    func `hides open AI web submenus when no history`() {\n        self.disableMenuCardsForTesting()\n        let settings = self.makeSettings()\n        settings.statusChecksEnabled = false\n        settings.refreshFrequency = .manual\n        settings.mergeIcons = true\n        settings.selectedMenuProvider = .codex\n\n        let registry = ProviderRegistry.shared\n        if let codexMeta = registry.metadata[.codex] {\n            settings.setProviderEnabled(provider: .codex, metadata: codexMeta, enabled: true)\n        }\n        if let claudeMeta = registry.metadata[.claude] {\n            settings.setProviderEnabled(provider: .claude, metadata: claudeMeta, enabled: false)\n        }\n        if let geminiMeta = registry.metadata[.gemini] {\n            settings.setProviderEnabled(provider: .gemini, metadata: geminiMeta, enabled: false)\n        }\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n        store.openAIDashboard = OpenAIDashboardSnapshot(\n            signedInEmail: \"user@example.com\",\n            codeReviewRemainingPercent: 100,\n            creditEvents: [],\n            dailyBreakdown: [],\n            usageBreakdown: [],\n            creditsPurchaseURL: nil,\n            updatedAt: Date())\n\n        let controller = StatusItemController(\n            store: store,\n            settings: settings,\n            account: fetcher.loadAccountInfo(),\n            updater: DisabledUpdaterController(),\n            preferencesSelection: PreferencesSelection(),\n            statusBar: self.makeStatusBarForTesting())\n\n        let menu = controller.makeMenu()\n        controller.menuWillOpen(menu)\n        let titles = Set(menu.items.map(\\.title))\n        #expect(!titles.contains(\"Credits history\"))\n        #expect(!titles.contains(\"Usage breakdown\"))\n    }\n\n    @Test\n    func `shows open AI web submenus when history exists`() throws {\n        self.disableMenuCardsForTesting()\n        let settings = SettingsStore(\n            configStore: testConfigStore(suiteName: \"StatusMenuTests-history\"),\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n        settings.statusChecksEnabled = false\n        settings.refreshFrequency = .manual\n        settings.mergeIcons = true\n        settings.selectedMenuProvider = .codex\n\n        let registry = ProviderRegistry.shared\n        if let codexMeta = registry.metadata[.codex] {\n            settings.setProviderEnabled(provider: .codex, metadata: codexMeta, enabled: true)\n        }\n        if let claudeMeta = registry.metadata[.claude] {\n            settings.setProviderEnabled(provider: .claude, metadata: claudeMeta, enabled: false)\n        }\n        if let geminiMeta = registry.metadata[.gemini] {\n            settings.setProviderEnabled(provider: .gemini, metadata: geminiMeta, enabled: false)\n        }\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n\n        let calendar = Calendar(identifier: .gregorian)\n        var components = DateComponents()\n        components.calendar = calendar\n        components.timeZone = TimeZone(secondsFromGMT: 0)\n        components.year = 2025\n        components.month = 12\n        components.day = 18\n        let date = try #require(components.date)\n\n        let events = [CreditEvent(date: date, service: \"CLI\", creditsUsed: 1)]\n        let breakdown = OpenAIDashboardSnapshot.makeDailyBreakdown(from: events, maxDays: 30)\n        store.openAIDashboard = OpenAIDashboardSnapshot(\n            signedInEmail: \"user@example.com\",\n            codeReviewRemainingPercent: 100,\n            creditEvents: events,\n            dailyBreakdown: breakdown,\n            usageBreakdown: breakdown,\n            creditsPurchaseURL: nil,\n            updatedAt: Date())\n\n        let controller = StatusItemController(\n            store: store,\n            settings: settings,\n            account: fetcher.loadAccountInfo(),\n            updater: DisabledUpdaterController(),\n            preferencesSelection: PreferencesSelection(),\n            statusBar: self.makeStatusBarForTesting())\n\n        let menu = controller.makeMenu()\n        controller.menuWillOpen(menu)\n        let usageItem = menu.items.first { ($0.representedObject as? String) == \"menuCardUsage\" }\n        let creditsItem = menu.items.first { ($0.representedObject as? String) == \"menuCardCredits\" }\n        #expect(\n            usageItem?.submenu?.items\n                .contains { ($0.representedObject as? String) == \"usageBreakdownChart\" } == true)\n        #expect(\n            creditsItem?.submenu?.items\n                .contains { ($0.representedObject as? String) == \"creditsHistoryChart\" } == true)\n    }\n\n    @Test\n    func `shows credits before cost in codex menu card sections`() throws {\n        self.disableMenuCardsForTesting()\n        let settings = self.makeSettings()\n        settings.statusChecksEnabled = false\n        settings.refreshFrequency = .manual\n        settings.mergeIcons = true\n        settings.selectedMenuProvider = .codex\n        settings.costUsageEnabled = true\n\n        let registry = ProviderRegistry.shared\n        if let codexMeta = registry.metadata[.codex] {\n            settings.setProviderEnabled(provider: .codex, metadata: codexMeta, enabled: true)\n        }\n        if let claudeMeta = registry.metadata[.claude] {\n            settings.setProviderEnabled(provider: .claude, metadata: claudeMeta, enabled: false)\n        }\n        if let geminiMeta = registry.metadata[.gemini] {\n            settings.setProviderEnabled(provider: .gemini, metadata: geminiMeta, enabled: false)\n        }\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n        store.credits = CreditsSnapshot(remaining: 100, events: [], updatedAt: Date())\n        store.openAIDashboard = OpenAIDashboardSnapshot(\n            signedInEmail: \"user@example.com\",\n            codeReviewRemainingPercent: 100,\n            creditEvents: [],\n            dailyBreakdown: [],\n            usageBreakdown: [],\n            creditsPurchaseURL: nil,\n            updatedAt: Date())\n        store._setTokenSnapshotForTesting(CostUsageTokenSnapshot(\n            sessionTokens: 123,\n            sessionCostUSD: 0.12,\n            last30DaysTokens: 123,\n            last30DaysCostUSD: 1.23,\n            daily: [\n                CostUsageDailyReport.Entry(\n                    date: \"2025-12-23\",\n                    inputTokens: nil,\n                    outputTokens: nil,\n                    totalTokens: 123,\n                    costUSD: 1.23,\n                    modelsUsed: nil,\n                    modelBreakdowns: nil),\n            ],\n            updatedAt: Date()), provider: .codex)\n\n        let controller = StatusItemController(\n            store: store,\n            settings: settings,\n            account: fetcher.loadAccountInfo(),\n            updater: DisabledUpdaterController(),\n            preferencesSelection: PreferencesSelection(),\n            statusBar: self.makeStatusBarForTesting())\n\n        let menu = controller.makeMenu()\n        controller.menuWillOpen(menu)\n        let ids = menu.items.compactMap { $0.representedObject as? String }\n        let creditsIndex = ids.firstIndex(of: \"menuCardCredits\")\n        let costIndex = ids.firstIndex(of: \"menuCardCost\")\n        #expect(creditsIndex != nil)\n        #expect(costIndex != nil)\n        #expect(try #require(creditsIndex) < costIndex!)\n    }\n\n    @Test\n    func `shows extra usage for claude when using menu card sections`() {\n        self.disableMenuCardsForTesting()\n        let settings = self.makeSettings()\n        settings.statusChecksEnabled = false\n        settings.refreshFrequency = .manual\n        settings.mergeIcons = true\n        settings.selectedMenuProvider = .claude\n        settings.costUsageEnabled = true\n        settings.claudeWebExtrasEnabled = true\n\n        let registry = ProviderRegistry.shared\n        if let codexMeta = registry.metadata[.codex] {\n            settings.setProviderEnabled(provider: .codex, metadata: codexMeta, enabled: false)\n        }\n        if let claudeMeta = registry.metadata[.claude] {\n            settings.setProviderEnabled(provider: .claude, metadata: claudeMeta, enabled: true)\n        }\n        if let geminiMeta = registry.metadata[.gemini] {\n            settings.setProviderEnabled(provider: .gemini, metadata: geminiMeta, enabled: false)\n        }\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n        let identity = ProviderIdentitySnapshot(\n            providerID: .claude,\n            accountEmail: \"user@example.com\",\n            accountOrganization: nil,\n            loginMethod: \"web\")\n        let snapshot = UsageSnapshot(\n            primary: RateWindow(usedPercent: 10, windowMinutes: nil, resetsAt: nil, resetDescription: \"Resets soon\"),\n            secondary: nil,\n            tertiary: nil,\n            providerCost: ProviderCostSnapshot(\n                used: 0,\n                limit: 2000,\n                currencyCode: \"EUR\",\n                period: \"Monthly\",\n                resetsAt: nil,\n                updatedAt: Date()),\n            updatedAt: Date(),\n            identity: identity)\n        store._setSnapshotForTesting(snapshot, provider: .claude)\n        store._setTokenSnapshotForTesting(CostUsageTokenSnapshot(\n            sessionTokens: 123,\n            sessionCostUSD: 0.12,\n            last30DaysTokens: 123,\n            last30DaysCostUSD: 1.23,\n            daily: [\n                CostUsageDailyReport.Entry(\n                    date: \"2025-12-23\",\n                    inputTokens: nil,\n                    outputTokens: nil,\n                    totalTokens: 123,\n                    costUSD: 1.23,\n                    modelsUsed: nil,\n                    modelBreakdowns: nil),\n            ],\n            updatedAt: Date()), provider: .claude)\n\n        let controller = StatusItemController(\n            store: store,\n            settings: settings,\n            account: fetcher.loadAccountInfo(),\n            updater: DisabledUpdaterController(),\n            preferencesSelection: PreferencesSelection(),\n            statusBar: self.makeStatusBarForTesting())\n\n        let menu = controller.makeMenu()\n        controller.menuWillOpen(menu)\n        let ids = menu.items.compactMap { $0.representedObject as? String }\n        #expect(ids.contains(\"menuCardExtraUsage\"))\n    }\n\n    @Test\n    func `shows vertex cost when usage error present`() {\n        self.disableMenuCardsForTesting()\n        let settings = self.makeSettings()\n        settings.statusChecksEnabled = false\n        settings.refreshFrequency = .manual\n        settings.mergeIcons = true\n        settings.selectedMenuProvider = .vertexai\n        settings.costUsageEnabled = true\n\n        let registry = ProviderRegistry.shared\n        if let vertexMeta = registry.metadata[.vertexai] {\n            settings.setProviderEnabled(provider: .vertexai, metadata: vertexMeta, enabled: true)\n        }\n        if let codexMeta = registry.metadata[.codex] {\n            settings.setProviderEnabled(provider: .codex, metadata: codexMeta, enabled: false)\n        }\n        if let claudeMeta = registry.metadata[.claude] {\n            settings.setProviderEnabled(provider: .claude, metadata: claudeMeta, enabled: false)\n        }\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n        store._setErrorForTesting(\"No Vertex AI usage data found for the current project.\", provider: .vertexai)\n        store._setTokenSnapshotForTesting(CostUsageTokenSnapshot(\n            sessionTokens: 10,\n            sessionCostUSD: 0.01,\n            last30DaysTokens: 100,\n            last30DaysCostUSD: 1.0,\n            daily: [\n                CostUsageDailyReport.Entry(\n                    date: \"2025-12-23\",\n                    inputTokens: nil,\n                    outputTokens: nil,\n                    totalTokens: 100,\n                    costUSD: 1.0,\n                    modelsUsed: nil,\n                    modelBreakdowns: nil),\n            ],\n            updatedAt: Date()), provider: .vertexai)\n\n        let controller = StatusItemController(\n            store: store,\n            settings: settings,\n            account: fetcher.loadAccountInfo(),\n            updater: DisabledUpdaterController(),\n            preferencesSelection: PreferencesSelection(),\n            statusBar: self.makeStatusBarForTesting())\n\n        let menu = controller.makeMenu()\n        controller.menuWillOpen(menu)\n        let ids = menu.items.compactMap { $0.representedObject as? String }\n        #expect(ids.contains(\"menuCardCost\"))\n    }\n}\n\nextension StatusMenuTests {\n    @Test\n    func `overview tab renders overview rows for all active providers when three or fewer`() {\n        self.disableMenuCardsForTesting()\n        let settings = self.makeSettings()\n        settings.statusChecksEnabled = false\n        settings.refreshFrequency = .manual\n        settings.mergeIcons = true\n        settings.selectedMenuProvider = .claude\n        settings.mergedMenuLastSelectedWasOverview = true\n\n        let registry = ProviderRegistry.shared\n        for provider in UsageProvider.allCases {\n            guard let metadata = registry.metadata[provider] else { continue }\n            let shouldEnable = provider == .codex || provider == .claude || provider == .cursor\n            settings.setProviderEnabled(provider: provider, metadata: metadata, enabled: shouldEnable)\n        }\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n        let controller = StatusItemController(\n            store: store,\n            settings: settings,\n            account: fetcher.loadAccountInfo(),\n            updater: DisabledUpdaterController(),\n            preferencesSelection: PreferencesSelection(),\n            statusBar: self.makeStatusBarForTesting())\n\n        let menu = controller.makeMenu()\n        controller.menuWillOpen(menu)\n\n        let ids = self.representedIDs(in: menu)\n        let overviewRows = ids.filter { $0.hasPrefix(\"overviewRow-\") }\n        #expect(overviewRows.count == 3)\n        #expect(overviewRows.contains(\"overviewRow-codex\"))\n        #expect(overviewRows.contains(\"overviewRow-claude\"))\n        #expect(overviewRows.contains(\"overviewRow-cursor\"))\n        #expect(ids.contains(\"menuCard\") == false)\n    }\n\n    @Test\n    func `overview tab honors stored subset when three or fewer`() {\n        self.disableMenuCardsForTesting()\n        let settings = self.makeSettings()\n        settings.statusChecksEnabled = false\n        settings.refreshFrequency = .manual\n        settings.mergeIcons = true\n        settings.selectedMenuProvider = .claude\n        settings.mergedMenuLastSelectedWasOverview = true\n\n        let registry = ProviderRegistry.shared\n        for provider in UsageProvider.allCases {\n            guard let metadata = registry.metadata[provider] else { continue }\n            let shouldEnable = provider == .codex || provider == .claude || provider == .cursor\n            settings.setProviderEnabled(provider: provider, metadata: metadata, enabled: shouldEnable)\n        }\n        _ = settings.setMergedOverviewProviderSelection(\n            provider: .claude,\n            isSelected: false,\n            activeProviders: [.codex, .claude, .cursor])\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n        let controller = StatusItemController(\n            store: store,\n            settings: settings,\n            account: fetcher.loadAccountInfo(),\n            updater: DisabledUpdaterController(),\n            preferencesSelection: PreferencesSelection(),\n            statusBar: self.makeStatusBarForTesting())\n\n        let menu = controller.makeMenu()\n        controller.menuWillOpen(menu)\n\n        let ids = self.representedIDs(in: menu)\n        let overviewRows = ids.filter { $0.hasPrefix(\"overviewRow-\") }\n        #expect(overviewRows.count == 2)\n        #expect(overviewRows.contains(\"overviewRow-codex\"))\n        #expect(overviewRows.contains(\"overviewRow-cursor\"))\n        #expect(overviewRows.contains(\"overviewRow-claude\") == false)\n        #expect(ids.contains(\"menuCard\") == false)\n    }\n\n    @Test\n    func `overview tab with explicit empty selection is hidden and shows provider detail`() {\n        self.disableMenuCardsForTesting()\n        let settings = self.makeSettings()\n        settings.statusChecksEnabled = false\n        settings.refreshFrequency = .manual\n        settings.mergeIcons = true\n        settings.selectedMenuProvider = .codex\n        settings.mergedMenuLastSelectedWasOverview = true\n        settings.mergedOverviewSelectedProviders = []\n\n        let registry = ProviderRegistry.shared\n        for provider in UsageProvider.allCases {\n            guard let metadata = registry.metadata[provider] else { continue }\n            let shouldEnable = provider == .codex ||\n                provider == .claude ||\n                provider == .cursor ||\n                provider == .opencode\n            settings.setProviderEnabled(provider: provider, metadata: metadata, enabled: shouldEnable)\n        }\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n        let controller = StatusItemController(\n            store: store,\n            settings: settings,\n            account: fetcher.loadAccountInfo(),\n            updater: DisabledUpdaterController(),\n            preferencesSelection: PreferencesSelection(),\n            statusBar: self.makeStatusBarForTesting())\n\n        let menu = controller.makeMenu()\n        controller.menuWillOpen(menu)\n\n        let ids = self.representedIDs(in: menu)\n        let switcherButtons = self.switcherButtons(in: menu)\n        #expect(switcherButtons.count == store.enabledProvidersForDisplay().count)\n        #expect(switcherButtons.contains(where: { $0.title == \"Overview\" }) == false)\n        #expect(switcherButtons.contains(where: { $0.state == .on && $0.tag == 0 }))\n        #expect(ids.contains(\"menuCard\"))\n        #expect(ids.contains(where: { $0.hasPrefix(\"overviewRow-\") }) == false)\n        #expect(ids.contains(\"overviewEmptyState\") == false)\n        #expect(menu.items.contains(where: { $0.title == \"No providers selected for Overview.\" }) == false)\n    }\n\n    @Test\n    func `overview rows keep menu item action in rendered mode`() throws {\n        StatusItemController.menuCardRenderingEnabled = true\n        StatusItemController.menuRefreshEnabled = false\n        defer { self.disableMenuCardsForTesting() }\n\n        let settings = self.makeSettings()\n        settings.statusChecksEnabled = false\n        settings.refreshFrequency = .manual\n        settings.mergeIcons = true\n        settings.selectedMenuProvider = .codex\n        settings.mergedMenuLastSelectedWasOverview = true\n\n        let registry = ProviderRegistry.shared\n        for provider in UsageProvider.allCases {\n            guard let metadata = registry.metadata[provider] else { continue }\n            let shouldEnable = provider == .codex || provider == .claude\n            settings.setProviderEnabled(provider: provider, metadata: metadata, enabled: shouldEnable)\n        }\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n        let controller = StatusItemController(\n            store: store,\n            settings: settings,\n            account: fetcher.loadAccountInfo(),\n            updater: DisabledUpdaterController(),\n            preferencesSelection: PreferencesSelection(),\n            statusBar: self.makeStatusBarForTesting())\n\n        let menu = controller.makeMenu()\n        controller.menuWillOpen(menu)\n\n        let claudeRow = try #require(menu.items.first {\n            ($0.representedObject as? String) == \"overviewRow-claude\"\n        })\n        #expect(claudeRow.action != nil)\n        #expect(claudeRow.target is StatusItemController)\n    }\n\n    @Test\n    func `selecting overview row switches to provider detail`() throws {\n        self.disableMenuCardsForTesting()\n        let settings = self.makeSettings()\n        settings.statusChecksEnabled = false\n        settings.refreshFrequency = .manual\n        settings.mergeIcons = true\n        settings.selectedMenuProvider = .codex\n        settings.mergedMenuLastSelectedWasOverview = true\n\n        let registry = ProviderRegistry.shared\n        for provider in UsageProvider.allCases {\n            guard let metadata = registry.metadata[provider] else { continue }\n            let shouldEnable = provider == .codex || provider == .claude\n            settings.setProviderEnabled(provider: provider, metadata: metadata, enabled: shouldEnable)\n        }\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n        let controller = StatusItemController(\n            store: store,\n            settings: settings,\n            account: fetcher.loadAccountInfo(),\n            updater: DisabledUpdaterController(),\n            preferencesSelection: PreferencesSelection(),\n            statusBar: self.makeStatusBarForTesting())\n\n        let menu = controller.makeMenu()\n        controller.menuWillOpen(menu)\n\n        let claudeRow = try #require(menu.items.first {\n            ($0.representedObject as? String) == \"overviewRow-claude\"\n        })\n        let action = try #require(claudeRow.action)\n        let target = try #require(claudeRow.target as? StatusItemController)\n        _ = target.perform(action, with: claudeRow)\n\n        #expect(settings.mergedMenuLastSelectedWasOverview == false)\n        #expect(settings.selectedMenuProvider == .claude)\n\n        let ids = self.representedIDs(in: menu)\n        #expect(ids.contains(\"menuCard\"))\n        #expect(ids.contains(where: { $0.hasPrefix(\"overviewRow-\") }) == false)\n        #expect(self.switcherButtons(in: menu).first(where: { $0.state == .on })?.tag == 2)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/StatusProbeTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n@testable import CodexBar\n\nstruct StatusProbeTests {\n    @Test\n    func `parse codex status`() throws {\n        let sample = \"\"\"\n        Model: gpt\n        Credits: 980 credits\n        5h limit: [#####] 75% left\n        Weekly limit: [##] 25% left\n        \"\"\"\n        let snap = try CodexStatusProbe.parse(text: sample)\n        #expect(snap.credits == 980)\n        #expect(snap.fiveHourPercentLeft == 75)\n        #expect(snap.weeklyPercentLeft == 25)\n    }\n\n    @Test\n    func `parse codex status with ansi and resets`() throws {\n        let sample = \"\"\"\n        \\u{001B}[38;5;245mCredits:\\u{001B}[0m 557 credits\n        5h limit: [█████     ] 50% left (resets 09:01)\n        Weekly limit: [███████   ] 85% left (resets 04:01 on 27 Nov)\n        \"\"\"\n        let snap = try CodexStatusProbe.parse(text: sample)\n        #expect(snap.credits == 557)\n        #expect(snap.fiveHourPercentLeft == 50)\n        #expect(snap.weeklyPercentLeft == 85)\n    }\n\n    @Test\n    func `parse claude status`() throws {\n        let sample = \"\"\"\n        Settings: Status   Config   Usage (tab to cycle)\n\n        Current session\n        1% used  (Resets 5am (Europe/Vienna))\n        Current week (all models)\n        1% used  (Resets Dec 2 at 12am (Europe/Vienna))\n        Current week (Sonnet only)\n        1% used (Resets Dec 2 at 12am (Europe/Vienna))\n\n        Nov 24, 2025 update:\n        We've increased your limits and removed the Opus cap,\n        so you can use Opus 4.5 up to your overall limit.\n        Sonnet now has its own limit—it's set to match your previous overall limit,\n        so you can use just as much as before.\n        Account: user@example.com\n        Org: Example Org\n        \"\"\"\n        let snap = try ClaudeStatusProbe.parse(text: sample)\n        #expect(snap.sessionPercentLeft == 99)\n        #expect(snap.weeklyPercentLeft == 99)\n        #expect(snap.opusPercentLeft == 99)\n        #expect(snap.accountEmail == \"user@example.com\")\n        #expect(snap.accountOrganization == \"Example Org\")\n        #expect(snap.primaryResetDescription == \"Resets 5am (Europe/Vienna)\")\n        #expect(snap.secondaryResetDescription == \"Resets Dec 2 at 12am (Europe/Vienna)\")\n        #expect(snap.opusResetDescription == \"Resets Dec 2 at 12am (Europe/Vienna)\")\n    }\n\n    @Test\n    func `parse claude status with ANSI`() throws {\n        let sample = \"\"\"\n        \\u{001B}[35mCurrent session\\u{001B}[0m\n        40% used  (Resets 11am)\n        Current week (all models)\n        10% used  (Resets Nov 27)\n        Current week (Sonnet only)\n        0% used (Resets Nov 27)\n        Account: user@example.com\n        Org: ACME\n        \\u{001B}[0m\n        \"\"\"\n        let snap = try ClaudeStatusProbe.parse(text: sample)\n        #expect(snap.sessionPercentLeft == 60)\n        #expect(snap.weeklyPercentLeft == 90)\n        #expect(snap.opusPercentLeft == 100)\n        #expect(snap.primaryResetDescription == \"Resets 11am\")\n        #expect(snap.secondaryResetDescription == \"Resets Nov 27\")\n        #expect(snap.opusResetDescription == \"Resets Nov 27\")\n    }\n\n    @Test\n    func `parse claude status legacy opus label`() throws {\n        let sample = \"\"\"\n        Current session\n        12% used  (Resets 11am)\n        Current week (all models)\n        55% used  (Resets Nov 21)\n        Current week (Opus)\n        5% used (Resets Nov 21)\n        Account: user@example.com\n        Org: Example Org\n        \"\"\"\n        let snap = try ClaudeStatusProbe.parse(text: sample)\n        #expect(snap.sessionPercentLeft == 88)\n        #expect(snap.weeklyPercentLeft == 45)\n        #expect(snap.opusPercentLeft == 95)\n        #expect(snap.primaryResetDescription == \"Resets 11am\")\n        #expect(snap.secondaryResetDescription == \"Resets Nov 21\")\n        #expect(snap.opusResetDescription == \"Resets Nov 21\")\n    }\n\n    @Test\n    func `parse claude status remaining keyword`() throws {\n        let sample = \"\"\"\n        Current session\n        12% remaining (Resets 11am)\n        Current week (all models)\n        40% remaining (Resets Nov 21)\n        \"\"\"\n        let snap = try ClaudeStatusProbe.parse(text: sample)\n        #expect(snap.sessionPercentLeft == 12)\n        #expect(snap.weeklyPercentLeft == 40)\n    }\n\n    @Test\n    func `parse claude status enterprise session only`() throws {\n        let sample = \"\"\"\n        Current session\n        █                                                  2% used\n        Resets 3pm (Europe/Vienna)\n        \"\"\"\n        let snap = try ClaudeStatusProbe.parse(text: sample)\n        #expect(snap.sessionPercentLeft == 98)\n        #expect(snap.weeklyPercentLeft == nil)\n        #expect(snap.primaryResetDescription == \"Resets 3pm (Europe/Vienna)\")\n        #expect(snap.secondaryResetDescription == nil)\n    }\n\n    @Test\n    func `parse claude status reset mappings with CR line endings`() throws {\n        let sample =\n            \"Current  session\\r\" +\n            \"██████████████████████████████████████████████████  17% used\\r\" +\n            \"Resets 12:59pm (Europe/Paris)\\r\" +\n            \"Current week (all models)\\r\" +\n            \"██████████████████████████████████████████████████   4% used\\r\" +\n            \"Resets Dec 24 at 3:59pm (Europe/Paris)\\r\" +\n            \"Current week (Sonnet only)\\r\" +\n            \"██████████████████████████████████████████████████   3% used\\r\" +\n            \"Resets Dec 23 at 3:59am (Europe/Paris)\\r\"\n\n        let snap = try ClaudeStatusProbe.parse(text: sample)\n        #expect(snap.sessionPercentLeft == 83)\n        #expect(snap.weeklyPercentLeft == 96)\n        #expect(snap.opusPercentLeft == 97)\n        #expect(snap.primaryResetDescription == \"Resets 12:59pm (Europe/Paris)\")\n        #expect(snap.secondaryResetDescription == \"Resets Dec 24 at 3:59pm (Europe/Paris)\")\n        #expect(snap.opusResetDescription == \"Resets Dec 23 at 3:59am (Europe/Paris)\")\n    }\n\n    @Test\n    func `parse claude status reset mappings does not promote weekly reset to session`() throws {\n        let sample = \"\"\"\n        Current session\n        ██████████████████████████████████████████████████  17% used\n        Current week (all models)\n        ██████████████████████████████████████████████████   4% used\n        Resets Dec 24 at 3:59pm (Europe/Paris)\n        \"\"\"\n        let snap = try ClaudeStatusProbe.parse(text: sample)\n        #expect(snap.sessionPercentLeft == 83)\n        #expect(snap.weeklyPercentLeft == 96)\n        #expect(snap.primaryResetDescription == nil)\n        #expect(snap.secondaryResetDescription == \"Resets Dec 24 at 3:59pm (Europe/Paris)\")\n    }\n\n    @Test\n    func `parse claude status with plan and ansi noise`() throws {\n        let sample = \"\"\"\n        Settings: Status   Config   Usage\n\n        Login method: \\u{001B}[22mClaude Max Account\\u{001B}[0m\n        Account: user@example.com\n        Org: ACME\n        \"\"\"\n        // Only care about login/identity; include minimal usage lines to satisfy parser.\n        let text = \"\"\"\n        Current session\n        10% used\n        Current week (all models)\n        20% used\n        Current week (Opus)\n        30% used\n        \\(sample)\n        \"\"\"\n        let snap = try ClaudeStatusProbe.parse(text: text)\n        #expect(snap.loginMethod == \"Max\")\n        #expect(snap.accountEmail == \"user@example.com\")\n        #expect(snap.accountOrganization == \"ACME\")\n    }\n\n    @Test\n    func `parse claude status with extra usage section`() throws {\n        let sample = \"\"\"\n        Settings:  Status   Config   Usage  (tab to cycle)\n\n         Current session\n         ▌                                                  1% used\n         Resets 3:59pm (Europe/Helsinki)\n\n         Current week (all models)\n         ▌                                                  1% used\n         Resets Jan 2, 2026, 10:59pm (Europe/Helsinki)\n\n         Current week (Sonnet only)\n                                                            0% used\n\n         Extra usage\n         Extra usage not enabled • /extra-usage to enable\n        \"\"\"\n\n        let snap = try ClaudeStatusProbe.parse(text: sample)\n        #expect(snap.sessionPercentLeft == 99)\n        #expect(snap.weeklyPercentLeft == 99)\n        #expect(snap.opusPercentLeft == 100)\n        #expect(snap.primaryResetDescription == \"Resets 3:59pm (Europe/Helsinki)\")\n        #expect(snap.secondaryResetDescription == \"Resets Jan 2, 2026, 10:59pm (Europe/Helsinki)\")\n    }\n\n    @Test\n    func `parse claude status ignores status bar context percent`() throws {\n        let sample = \"\"\"\n        Claude Code v2.1.29\n        22:47 |  | Opus 4.5 | default | ░░░░░░░░░░ 0%  ◯ /ide for Visual Studio Code\n\n        Settings:  Status   Config   Usage  (tab to cycle)\n        Loading usage data…\n        Esc to cancel\n\n        Curretsession\n        ███████▌15%used\n        Resets 11:30pm (Asia/Calcutta)\n\n        Current week (all models)\n        █▌                                                 3% used\n        Resets Feb 12 at 1:30pm (Asia/Calcutta)\n\n        Current week (Sonnet only)\n        ▌                                                  1% used\n        Resets Feb 12 at 1:30pm (Asia/Calcutta)\n        \"\"\"\n\n        let snap = try ClaudeStatusProbe.parse(text: sample)\n        #expect(snap.sessionPercentLeft == 85)\n        #expect(snap.weeklyPercentLeft == 97)\n        #expect(snap.opusPercentLeft == 99)\n    }\n\n    @Test\n    func `parse claude status loading panel does not report zero percent`() {\n        let sample = \"\"\"\n        Claude Code v2.1.29\n        22:47 |  | Opus 4.5 | default | ░░░░░░░░░░ 0%  ◯ /ide for Visual Studio Code\n\n        Settings:  Status   Config   Usage  (tab to cycle)\n        Loading usage data…\n        Esc to cancel\n        \"\"\"\n\n        do {\n            _ = try ClaudeStatusProbe.parse(text: sample)\n            #expect(Bool(false), \"Parsing should fail while /usage is still loading\")\n        } catch ClaudeStatusProbeError.parseFailed {\n            return\n        } catch ClaudeStatusProbeError.timedOut {\n            return\n        } catch {\n            #expect(Bool(false), \"Unexpected error: \\(error)\")\n        }\n    }\n\n    @Test\n    func `parse claude status status only output does not fallback to zero`() {\n        let sample = \"\"\"\n        Claude Code v2.1.32\n        01:07 |  | Opus 4.6 | default | ░░░░░░░░░░ 0% left\n        Status: Partially Degraded Service\n        /status\n        \"\"\"\n\n        do {\n            _ = try ClaudeStatusProbe.parse(text: sample)\n            #expect(Bool(false), \"Parsing should fail when /usage windows are missing\")\n        } catch ClaudeStatusProbeError.parseFailed {\n            return\n        } catch ClaudeStatusProbeError.timedOut {\n            return\n        } catch {\n            #expect(Bool(false), \"Unexpected error: \\(error)\")\n        }\n    }\n\n    @Test\n    func `parse claude status placeholder usage window does not use status bar percent`() {\n        let sample = \"\"\"\n        Claude Code v2.1.32\n        01:07 |  | Opus 4.6 | default | ░░░░░░░░░░ 0% left\n        Settings: Status   Config   Usage\n        Current session\n        Current week (all models)\n        Current week (Sonnet only)\n        \"\"\"\n\n        do {\n            _ = try ClaudeStatusProbe.parse(text: sample)\n            #expect(Bool(false), \"Parsing should fail when only status-bar percentages are present\")\n        } catch ClaudeStatusProbeError.parseFailed {\n            return\n        } catch ClaudeStatusProbeError.timedOut {\n            return\n        } catch {\n            #expect(Bool(false), \"Unexpected error: \\(error)\")\n        }\n    }\n\n    @Test\n    func `parse claude status compact markers still parse`() throws {\n        let sample = \"\"\"\n        Settings:StatusConfigUsage(←/→ortabtocycle)\n        Loadingusagedata…\n        Curretsession\n        ███6%used\n        Resets4:29am(Asia/Calcutta)\n        Currentweek(allmodels)\n        ██4%used\n        ResetsFeb12at1:29pm(Asia/Calcutta)\n        Currentweek(Sonnetonly)\n        ▌1%used\n        ResetsFeb12at1:29pm(Asia/Calcutta)\n        \"\"\"\n\n        let snap = try ClaudeStatusProbe.parse(text: sample)\n        #expect(snap.sessionPercentLeft == 94)\n        #expect(snap.weeklyPercentLeft == 96)\n        #expect(snap.opusPercentLeft == 99)\n        #expect(snap.secondaryResetDescription == \"ResetsFeb12at1:29pm(Asia/Calcutta)\")\n        #expect(snap.opusResetDescription == \"ResetsFeb12at1:29pm(Asia/Calcutta)\")\n    }\n\n    @Test\n    func `parse claude status with bracket plan noise no esc`() throws {\n        let sample = \"\"\"\n        Login method: [22m Claude Max Account\n        Account: user@example.com\n        \"\"\"\n        let text = \"\"\"\n        Current session\n        10% used\n        Current week (all models)\n        20% used\n        Current week (Opus)\n        30% used\n        \\(sample)\n        \"\"\"\n        let snap = try ClaudeStatusProbe.parse(text: text)\n        #expect(snap.loginMethod == \"Max\")\n    }\n\n    @Test\n    func `surfaces claude token expired`() {\n        let sample = \"\"\"\n        Settings:  Status   Config   Usage\n\n        Error: Failed to load usage data: {\"type\":\"error\",\"error\":{\"type\":\"authentication_error\",\n        \"message\":\"OAuth token has expired. Please obtain a new token or refresh your existing token.\",\n        \"details\":{\"error_visibility\":\"user_facing\",\"error_code\":\"token_expired\"}},\\\n        \"request_id\":\"req_123\"}\n        \"\"\"\n\n        do {\n            _ = try ClaudeStatusProbe.parse(text: sample)\n            #expect(Bool(false), \"Parsing should fail for auth error\")\n        } catch let ClaudeStatusProbeError.parseFailed(message) {\n            let lower = message.lowercased()\n            #expect(lower.contains(\"token\"))\n            #expect(lower.contains(\"login\"))\n        } catch {\n            #expect(Bool(false), \"Unexpected error: \\(error)\")\n        }\n    }\n\n    @Test\n    func `surfaces claude rate limited compact usage error`() {\n        let sample = \"\"\"\n        Settings:StatusConfigUsage(←/→ortabtocycle)\n        Error:Failedtoloadusagedata:{\"error\":{\"message\":\"Ratelimited.Pleasetryagainlater.\",\"type\":\"rate_limit_error\"}}\n        \"\"\"\n\n        do {\n            _ = try ClaudeStatusProbe.parse(text: sample)\n            #expect(Bool(false), \"Parsing should fail for rate limiting\")\n        } catch let ClaudeStatusProbeError.parseFailed(message) {\n            let lower = message.lowercased()\n            #expect(lower.contains(\"rate\"))\n            #expect(lower.contains(\"limit\"))\n        } catch {\n            #expect(Bool(false), \"Unexpected error: \\(error)\")\n        }\n    }\n\n    @Test\n    func `surfaces claude folder trust prompt`() {\n        let sample = \"\"\"\n        Do you trust the files in this folder?\n\n        /Users/example/project\n        \"\"\"\n\n        do {\n            _ = try ClaudeStatusProbe.parse(text: sample)\n            #expect(Bool(false), \"Parsing should fail for folder trust prompt\")\n        } catch let ClaudeStatusProbeError.parseFailed(message) {\n            #expect(message.lowercased().contains(\"trust\"))\n            #expect(message.contains(\"/Users/example/project\"))\n            #expect(message.contains(\"cd \\\"/Users/example/project\\\" && claude\"))\n        } catch {\n            #expect(Bool(false), \"Unexpected error: \\(error)\")\n        }\n    }\n\n    @Test\n    func `surfaces claude folder trust prompt with CRLF and spaces`() {\n        let sample = \"Do you trust the files in this folder?\\r\\n\\r\\n/Users/example/My Project\\r\\n\"\n\n        do {\n            _ = try ClaudeStatusProbe.parse(text: sample)\n            #expect(Bool(false), \"Parsing should fail for folder trust prompt\")\n        } catch let ClaudeStatusProbeError.parseFailed(message) {\n            #expect(message.contains(\"/Users/example/My Project\"))\n            #expect(message.contains(\"cd \\\"/Users/example/My Project\\\" && claude\"))\n        } catch {\n            #expect(Bool(false), \"Unexpected error: \\(error)\")\n        }\n    }\n\n    @Test\n    func `surfaces claude folder trust prompt without folder path`() {\n        let sample = \"\"\"\n        Do you trust the files in this folder?\n        \"\"\"\n\n        do {\n            _ = try ClaudeStatusProbe.parse(text: sample)\n            #expect(Bool(false), \"Parsing should fail for folder trust prompt\")\n        } catch let ClaudeStatusProbeError.parseFailed(message) {\n            let lower = message.lowercased()\n            #expect(lower.contains(\"trust\"))\n            #expect(lower.contains(\"auto-accept\"))\n        } catch {\n            #expect(Bool(false), \"Unexpected error: \\(error)\")\n        }\n    }\n\n    @Test\n    func `parses claude reset time only`() throws {\n        let now = Date(timeIntervalSince1970: 1_733_690_000)\n        let parsed = ClaudeStatusProbe.parseResetDate(from: \"Resets 12:59pm (Europe/Helsinki)\", now: now)\n        let tz = try #require(TimeZone(identifier: \"Europe/Helsinki\"))\n        var calendar = Calendar(identifier: .gregorian)\n        calendar.timeZone = tz\n        var expected = try #require(calendar.date(bySettingHour: 12, minute: 59, second: 0, of: now))\n        if expected < now {\n            expected = try #require(calendar.date(byAdding: .day, value: 1, to: expected))\n        }\n        #expect(parsed == expected)\n    }\n\n    @Test\n    func `parses claude reset date and time`() throws {\n        let now = Date(timeIntervalSince1970: 1_733_690_000)\n        let parsed = ClaudeStatusProbe.parseResetDate(from: \"Resets Dec 9, 8:59am (Europe/Helsinki)\", now: now)\n        var calendar = Calendar(identifier: .gregorian)\n        calendar.timeZone = try #require(TimeZone(identifier: \"Europe/Helsinki\"))\n        let expected = calendar.date(from: DateComponents(\n            year: calendar.component(.year, from: now),\n            month: 12,\n            day: 9,\n            hour: 8,\n            minute: 59,\n            second: 0))\n        #expect(parsed == expected)\n    }\n\n    @Test\n    func `parses claude reset with dot separated time`() throws {\n        let now = Date(timeIntervalSince1970: 1_733_690_000)\n        let parsed = ClaudeStatusProbe.parseResetDate(from: \"Resets Dec 9 at 5.27am (UTC)\", now: now)\n        var calendar = Calendar(identifier: .gregorian)\n        calendar.timeZone = try #require(TimeZone(identifier: \"UTC\"))\n        let expected = calendar.date(from: DateComponents(year: 2024, month: 12, day: 9, hour: 5, minute: 27))\n        #expect(parsed == expected)\n    }\n\n    @Test\n    func `parses claude reset with compact times`() throws {\n        let now = Date(timeIntervalSince1970: 1_733_690_000)\n        let parsedTimeOnly = ClaudeStatusProbe.parseResetDate(from: \"Resets 1pm (UTC)\", now: now)\n        var calendar = Calendar(identifier: .gregorian)\n        calendar.timeZone = try #require(TimeZone(identifier: \"UTC\"))\n        var expected = try #require(calendar.date(bySettingHour: 13, minute: 0, second: 0, of: now))\n        if expected < now {\n            expected = try #require(calendar.date(byAdding: .day, value: 1, to: expected))\n        }\n        #expect(parsedTimeOnly == expected)\n\n        let parsedDateTime = ClaudeStatusProbe.parseResetDate(from: \"Resets Dec 9, 9am\", now: now)\n        calendar.timeZone = TimeZone.current\n        let dateExpected = calendar.date(from: DateComponents(\n            year: calendar.component(.year, from: now),\n            month: 12,\n            day: 9,\n            hour: 9,\n            minute: 0,\n            second: 0))\n        #expect(parsedDateTime == dateExpected)\n    }\n\n    @Test\n    func `parses claude reset with compact date and time no spaces`() throws {\n        let now = Date(timeIntervalSince1970: 1_773_097_200) // Mar 10, 2026 12:00:00 UTC\n        let parsed = ClaudeStatusProbe.parseResetDate(from: \"ResetsMar13at12:30pm(Asia/Calcutta)\", now: now)\n        var calendar = Calendar(identifier: .gregorian)\n        calendar.timeZone = try #require(TimeZone(identifier: \"Asia/Calcutta\"))\n        let expected = calendar.date(from: DateComponents(year: 2026, month: 3, day: 13, hour: 12, minute: 30))\n        #expect(parsed == expected)\n    }\n\n    @Test\n    func `live codex status`() async throws {\n        guard ProcessInfo.processInfo.environment[\"LIVE_CODEX_STATUS\"] == \"1\" else { return }\n\n        let probe = CodexStatusProbe()\n        do {\n            let snap = try await probe.fetch()\n            let summary = \"\"\"\n            Live Codex status:\n            \\(snap.rawText)\n            values: 5h \\(snap.fiveHourPercentLeft ?? -1)% left,\n            weekly \\(snap.weeklyPercentLeft ?? -1)% left,\n            credits \\(snap.credits ?? -1)\n            \"\"\"\n            print(summary)\n        } catch {\n            // Dump raw PTY text to help debug.\n            let runner = TTYCommandRunner()\n            let res = try runner.run(\n                binary: \"codex\",\n                send: \"/status\\n\",\n                options: .init(rows: 60, cols: 200, timeout: 12))\n            print(\"RAW CODEX PTY OUTPUT BEGIN\\n\\(res.text)\\nRAW CODEX PTY OUTPUT END\")\n            let clean = TextParsing.stripANSICodes(res.text)\n            print(\"CLEAN CODEX OUTPUT BEGIN\\n\\(clean)\\nCLEAN CODEX OUTPUT END\")\n            let five = TextParsing.firstInt(pattern: #\"5h limit[^\\n]*?([0-9]{1,3})%\\s+left\"#, text: clean) ?? -1\n            let week = TextParsing.firstInt(pattern: #\"Weekly limit[^\\n]*?([0-9]{1,3})%\\s+left\"#, text: clean) ?? -1\n            let credits = TextParsing.firstNumber(pattern: #\"Credits:\\s*([0-9][0-9.,]*)\"#, text: clean) ?? -1\n            print(\"Parsed probes => 5h \\(five)% weekly \\(week)% credits \\(credits)\")\n            throw error\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/SubprocessRunnerTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\nstruct SubprocessRunnerTests {\n    @Test\n    func `reads large stdout without deadlock`() async throws {\n        let result = try await SubprocessRunner.run(\n            binary: \"/usr/bin/python3\",\n            arguments: [\"-c\", \"print('x' * 1_000_000)\"],\n            environment: ProcessInfo.processInfo.environment,\n            timeout: 5,\n            label: \"python large stdout\")\n\n        #expect(result.stdout.count >= 1_000_000)\n        #expect(result.stderr.isEmpty)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/SubscriptionDetectionTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBar\n\nstruct SubscriptionDetectionTests {\n    // MARK: - Subscription plans should be detected\n\n    @Test\n    func `detects max plan`() {\n        #expect(UsageStore.isSubscriptionPlan(\"Claude Max\") == true)\n        #expect(UsageStore.isSubscriptionPlan(\"Max\") == true)\n        #expect(UsageStore.isSubscriptionPlan(\"claude max\") == true)\n        #expect(UsageStore.isSubscriptionPlan(\"MAX\") == true)\n    }\n\n    @Test\n    func `detects pro plan`() {\n        #expect(UsageStore.isSubscriptionPlan(\"Claude Pro\") == true)\n        #expect(UsageStore.isSubscriptionPlan(\"Pro\") == true)\n        #expect(UsageStore.isSubscriptionPlan(\"pro\") == true)\n    }\n\n    @Test\n    func `detects ultra plan`() {\n        #expect(UsageStore.isSubscriptionPlan(\"Claude Ultra\") == true)\n        #expect(UsageStore.isSubscriptionPlan(\"Ultra\") == true)\n        #expect(UsageStore.isSubscriptionPlan(\"ultra\") == true)\n    }\n\n    @Test\n    func `detects team plan`() {\n        #expect(UsageStore.isSubscriptionPlan(\"Claude Team\") == true)\n        #expect(UsageStore.isSubscriptionPlan(\"Team\") == true)\n        #expect(UsageStore.isSubscriptionPlan(\"team\") == true)\n    }\n\n    @Test\n    func `enterprise plan does not count as subscription`() {\n        #expect(UsageStore.isSubscriptionPlan(\"Claude Enterprise\") == false)\n        #expect(UsageStore.isSubscriptionPlan(\"Enterprise\") == false)\n    }\n\n    // MARK: - Non-subscription plans should return false\n\n    @Test\n    func `nil login method returns false`() {\n        #expect(UsageStore.isSubscriptionPlan(nil) == false)\n    }\n\n    @Test\n    func `empty login method returns false`() {\n        #expect(UsageStore.isSubscriptionPlan(\"\") == false)\n        #expect(UsageStore.isSubscriptionPlan(\"   \") == false)\n    }\n\n    @Test\n    func `unknown plan returns false`() {\n        #expect(UsageStore.isSubscriptionPlan(\"API\") == false)\n        #expect(UsageStore.isSubscriptionPlan(\"Free\") == false)\n        #expect(UsageStore.isSubscriptionPlan(\"Unknown\") == false)\n        #expect(UsageStore.isSubscriptionPlan(\"Claude\") == false)\n    }\n\n    @Test\n    func `api key users return false`() {\n        // API users typically don't have a login method or have non-subscription identifiers\n        #expect(UsageStore.isSubscriptionPlan(\"api_key\") == false)\n        #expect(UsageStore.isSubscriptionPlan(\"console\") == false)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/SyntheticProviderTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\nstruct SyntheticSettingsReaderTests {\n    @Test\n    func `api key reads from environment`() {\n        let token = SyntheticSettingsReader.apiKey(environment: [\"SYNTHETIC_API_KEY\": \"abc123\"])\n        #expect(token == \"abc123\")\n    }\n\n    @Test\n    func `api key strips quotes`() {\n        let token = SyntheticSettingsReader.apiKey(environment: [\"SYNTHETIC_API_KEY\": \"\\\"token-xyz\\\"\"])\n        #expect(token == \"token-xyz\")\n    }\n}\n\nstruct SyntheticUsageSnapshotTests {\n    @Test\n    func `maps usage snapshot windows`() throws {\n        let json = \"\"\"\n        {\n          \"plan\": \"Starter\",\n          \"quotas\": [\n            { \"name\": \"Monthly\", \"limit\": 1000, \"used\": 250, \"reset_at\": \"2025-01-01T00:00:00Z\" },\n            { \"name\": \"Daily\", \"max\": 200, \"remaining\": 50, \"window_minutes\": 1440 }\n          ]\n        }\n        \"\"\"\n        let data = try #require(json.data(using: .utf8))\n        let snapshot = try SyntheticUsageParser.parse(data: data, now: Date(timeIntervalSince1970: 123))\n        let usage = snapshot.toUsageSnapshot()\n\n        #expect(usage.primary?.usedPercent == 25)\n        #expect(usage.secondary?.usedPercent == 75)\n        #expect(usage.secondary?.windowMinutes == 1440)\n        #expect(usage.loginMethod(for: .synthetic) == \"Starter\")\n    }\n\n    @Test\n    func `parses subscription quota`() throws {\n        let json = \"\"\"\n        {\n          \"subscription\": {\n            \"limit\": 1350,\n            \"requests\": 73.8,\n            \"renewsAt\": \"2026-01-11T11:23:38.600Z\"\n          }\n        }\n        \"\"\"\n        let data = try #require(json.data(using: .utf8))\n        let snapshot = try SyntheticUsageParser.parse(data: data, now: Date(timeIntervalSince1970: 123))\n        let usage = snapshot.toUsageSnapshot()\n        let expected = (73.8 / 1350.0) * 100\n        let formatter = ISO8601DateFormatter()\n        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]\n        let expectedReset = try #require(formatter.date(from: \"2026-01-11T11:23:38.600Z\"))\n\n        #expect(abs((usage.primary?.usedPercent ?? 0) - expected) < 0.01)\n        #expect(usage.primary?.resetsAt == expectedReset)\n        #expect(usage.loginMethod(for: .synthetic) == nil)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/TTYCommandRunnerTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\n@Suite(.serialized)\nstruct TTYCommandRunnerEnvTests {\n    @Test\n    func `shutdown fence drains tracked TTY processes`() {\n        TTYCommandRunner._test_resetTrackedProcesses()\n        defer { TTYCommandRunner._test_resetTrackedProcesses() }\n\n        #expect(TTYCommandRunner._test_registerTrackedProcess(pid: 1001, binary: \"codex\"))\n        #expect(TTYCommandRunner._test_trackedProcessCount() == 1)\n\n        let drained = TTYCommandRunner._test_drainTrackedProcessesForShutdown()\n        #expect(drained.count == 1)\n        #expect(drained[0].pid == 1001)\n        #expect(TTYCommandRunner._test_trackedProcessCount() == 0)\n    }\n\n    @Test\n    func `tracked process helpers ignore invalid PID`() {\n        TTYCommandRunner._test_resetTrackedProcesses()\n        defer { TTYCommandRunner._test_resetTrackedProcesses() }\n\n        TTYCommandRunner._test_trackProcess(pid: 0, binary: \"codex\", processGroup: nil)\n        #expect(TTYCommandRunner._test_trackedProcessCount() == 0)\n    }\n\n    @Test\n    func `shutdown fence rejects new registrations`() {\n        TTYCommandRunner._test_resetTrackedProcesses()\n        defer { TTYCommandRunner._test_resetTrackedProcesses() }\n\n        #expect(TTYCommandRunner._test_registerTrackedProcess(pid: 2001, binary: \"codex\"))\n        let drained = TTYCommandRunner._test_drainTrackedProcessesForShutdown()\n        #expect(drained.count == 1)\n\n        #expect(TTYCommandRunner._test_registerTrackedProcess(pid: 2002, binary: \"codex\") == false)\n        #expect(TTYCommandRunner._test_trackedProcessCount() == 0)\n    }\n\n    @Test\n    func `shutdown resolver skips host process group fallback`() {\n        let hostGroup: pid_t = 4242\n        let targets: [(pid: pid_t, binary: String, processGroup: pid_t?)] = [\n            (pid: 100, binary: \"codex\", processGroup: nil),\n            (pid: 101, binary: \"codex\", processGroup: hostGroup),\n            (pid: 102, binary: \"codex\", processGroup: 7777),\n        ]\n\n        let resolved = TTYCommandRunner._test_resolveShutdownTargets(\n            targets,\n            hostProcessGroup: hostGroup,\n            groupResolver: { pid in\n                pid == 100 ? hostGroup : -1\n            })\n\n        #expect(resolved.count == 3)\n        #expect(resolved[0].processGroup == nil)\n        #expect(resolved[1].processGroup == nil)\n        #expect(resolved[2].processGroup == 7777)\n    }\n\n    @Test\n    func `preserves environment and sets term`() {\n        let baseEnv: [String: String] = [\n            \"PATH\": \"/custom/bin\",\n            \"HOME\": \"/Users/tester\",\n            \"LANG\": \"en_US.UTF-8\",\n        ]\n\n        let merged = TTYCommandRunner.enrichedEnvironment(\n            baseEnv: baseEnv,\n            loginPATH: nil,\n            home: \"/Users/tester\")\n\n        #expect(merged[\"HOME\"] == \"/Users/tester\")\n        #expect(merged[\"LANG\"] == \"en_US.UTF-8\")\n        #expect(merged[\"TERM\"] == \"xterm-256color\")\n\n        #expect(merged[\"PATH\"] == \"/custom/bin\")\n    }\n\n    @Test\n    func `backfills home when missing`() {\n        let merged = TTYCommandRunner.enrichedEnvironment(\n            baseEnv: [\"PATH\": \"/custom/bin\"],\n            loginPATH: nil,\n            home: \"/Users/fallback\")\n        #expect(merged[\"HOME\"] == \"/Users/fallback\")\n        #expect(merged[\"TERM\"] == \"xterm-256color\")\n    }\n\n    @Test\n    func `preserves existing term and custom vars`() {\n        let merged = TTYCommandRunner.enrichedEnvironment(\n            baseEnv: [\n                \"PATH\": \"/custom/bin\",\n                \"TERM\": \"vt100\",\n                \"BUN_INSTALL\": \"/Users/tester/.bun\",\n                \"SHELL\": \"/bin/zsh\",\n            ],\n            loginPATH: nil,\n            home: \"/Users/tester\")\n\n        #expect(merged[\"TERM\"] == \"vt100\")\n        #expect(merged[\"BUN_INSTALL\"] == \"/Users/tester/.bun\")\n        #expect(merged[\"SHELL\"] == \"/bin/zsh\")\n        #expect((merged[\"PATH\"] ?? \"\").contains(\"/custom/bin\"))\n    }\n\n    @Test\n    func `sets working directory when provided`() throws {\n        let fm = FileManager.default\n        let dir = fm.temporaryDirectory.appendingPathComponent(\"codexbar-tty-\\(UUID().uuidString)\", isDirectory: true)\n        try fm.createDirectory(at: dir, withIntermediateDirectories: true)\n\n        let runner = TTYCommandRunner()\n        let result = try runner.run(binary: \"/bin/pwd\", send: \"\", options: .init(timeout: 3, workingDirectory: dir))\n        let clean = result.text.replacingOccurrences(of: \"\\r\", with: \"\")\n        #expect(clean.contains(dir.path))\n    }\n\n    @Test\n    func `auto responds to trust prompt`() throws {\n        let fm = FileManager.default\n        let dir = fm.temporaryDirectory.appendingPathComponent(\"codexbar-tty-\\(UUID().uuidString)\", isDirectory: true)\n        try fm.createDirectory(at: dir, withIntermediateDirectories: true)\n        defer { try? fm.removeItem(at: dir) }\n\n        let scriptURL = dir.appendingPathComponent(\"trust.sh\")\n        let script = \"\"\"\n        #!/bin/sh\n        echo \\\"Do you trust the files in this folder?\\\"\n        echo \\\"\\\"\n        echo \\\"/Users/example/project\\\"\n        IFS= read -r ans\n        if [ \\\"$ans\\\" = \\\"y\\\" ] || [ \\\"$ans\\\" = \\\"Y\\\" ]; then\n          echo \\\"accepted\\\"\n        else\n          echo \\\"rejected:$ans\\\"\n        fi\n        \"\"\"\n        try script.write(to: scriptURL, atomically: true, encoding: .utf8)\n        try fm.setAttributes([.posixPermissions: 0o755], ofItemAtPath: scriptURL.path)\n\n        let runner = TTYCommandRunner()\n        let result = try runner.run(\n            binary: scriptURL.path,\n            send: \"\",\n            options: .init(\n                timeout: 6,\n                // Use LF for portability: some PTY/termios setups do not translate CR → NL for shell reads.\n                sendOnSubstrings: [\"trust the files in this folder?\": \"y\\n\"],\n                stopOnSubstrings: [\"accepted\", \"rejected\"],\n                settleAfterStop: 0.1))\n\n        #expect(result.text.contains(\"accepted\"))\n    }\n\n    @Test\n    func `stops when output is idle`() throws {\n        let fm = FileManager.default\n        let dir = fm.temporaryDirectory.appendingPathComponent(\"codexbar-tty-\\(UUID().uuidString)\", isDirectory: true)\n        try fm.createDirectory(at: dir, withIntermediateDirectories: true)\n        defer { try? fm.removeItem(at: dir) }\n\n        let scriptURL = dir.appendingPathComponent(\"idle.sh\")\n        let script = \"\"\"\n        #!/bin/sh\n        echo \"hello\"\n        sleep 30\n        \"\"\"\n        try script.write(to: scriptURL, atomically: true, encoding: .utf8)\n        try fm.setAttributes([.posixPermissions: 0o755], ofItemAtPath: scriptURL.path)\n\n        let runner = TTYCommandRunner()\n        let timeout: TimeInterval = 6\n        var fastestElapsed = TimeInterval.greatestFiniteMagnitude\n        // CI can occasionally pause a test process long enough to miss an idle window.\n        // Retry once and assert that at least one run exits well before timeout.\n        for _ in 0..<2 {\n            let startedAt = Date()\n            let result = try runner.run(\n                binary: scriptURL.path,\n                send: \"\",\n                options: .init(timeout: timeout, idleTimeout: 0.2))\n            let elapsed = Date().timeIntervalSince(startedAt)\n\n            #expect(result.text.contains(\"hello\"))\n            fastestElapsed = min(fastestElapsed, elapsed)\n        }\n        #expect(fastestElapsed < (timeout - 1.0))\n    }\n\n    @Test\n    func `rolling buffer detects needle across boundary`() {\n        var scanner = TTYCommandRunner.RollingBuffer(maxNeedle: 6)\n        let needle = Data(\"hello\".utf8)\n        let first = scanner.append(Data(\"he\".utf8))\n        #expect(first.range(of: needle) == nil)\n        let second = scanner.append(Data(\"llo!\".utf8))\n        #expect(second.range(of: needle) != nil)\n    }\n\n    @Test\n    func `lowercased ASCII only touches ascii`() {\n        let data = Data(\"UpDaTe\".utf8)\n        let lowered = TTYCommandRunner.lowercasedASCII(data)\n        #expect(String(data: lowered, encoding: .utf8) == \"update\")\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/TTYIntegrationTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBar\n@testable import CodexBarCore\n\n@Suite(.serialized)\nstruct TTYIntegrationTests {\n    @Test\n    func `codex RPC usage live`() async throws {\n        let fetcher = UsageFetcher()\n        do {\n            let snapshot = try await fetcher.loadLatestUsage()\n            guard let primary = snapshot.primary else {\n                return\n            }\n            let hasData = primary.usedPercent >= 0 && (snapshot.secondary?.usedPercent ?? 0) >= 0\n            #expect(hasData)\n        } catch UsageError.noRateLimitsFound {\n            return\n        } catch {\n            return\n        }\n    }\n\n    @Test\n    func `claude TTY usage probe live`() async throws {\n        guard ProcessInfo.processInfo.environment[\"LIVE_CLAUDE_TTY\"] == \"1\" else {\n            return\n        }\n        guard TTYCommandRunner.which(\"claude\") != nil else {\n            return\n        }\n\n        let fetcher = ClaudeUsageFetcher(browserDetection: BrowserDetection(cacheTTL: 0), dataSource: .cli)\n        defer { Task { await ClaudeCLISession.shared.reset() } }\n\n        var shouldAssert = true\n        do {\n            let snapshot = try await fetcher.loadLatestUsage()\n            #expect(snapshot.primary.remainingPercent >= 0)\n            // Weekly is absent for some enterprise accounts.\n        } catch ClaudeUsageError.parseFailed(_) {\n            shouldAssert = false\n        } catch ClaudeStatusProbeError.parseFailed(_) {\n            shouldAssert = false\n        } catch ClaudeUsageError.claudeNotInstalled {\n            shouldAssert = false\n        } catch ClaudeStatusProbeError.timedOut {\n            shouldAssert = false\n        } catch let TTYCommandRunner.Error.launchFailed(message) where message.contains(\"login\") {\n            shouldAssert = false\n        } catch {\n            shouldAssert = false\n        }\n\n        if !shouldAssert { return }\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/TestProcessCleanup.swift",
    "content": "import Foundation\n\n#if canImport(Darwin)\nimport Darwin\n#else\nimport Glibc\n#endif\n\nenum TestProcessCleanup {\n    static func register() {\n        atexit(_testProcessCleanupAtExit)\n    }\n\n    fileprivate static func terminateLeakedCodexAppServers() {\n        let pids = Self.pids(matchingFullCommandRegex: \"codex.*app-server\")\n            .filter { $0 > 0 && $0 != getpid() }\n        guard !pids.isEmpty else { return }\n\n        for pid in pids {\n            _ = kill(pid, SIGTERM)\n        }\n\n        let deadline = Date().addingTimeInterval(0.6)\n        while Date() < deadline {\n            let stillRunning = pids.contains(where: { kill($0, 0) == 0 })\n            if !stillRunning { return }\n            usleep(50000)\n        }\n\n        for pid in pids where kill(pid, 0) == 0 {\n            _ = kill(pid, SIGKILL)\n        }\n    }\n\n    private static func pids(matchingFullCommandRegex regex: String) -> [pid_t] {\n        let proc = Process()\n        proc.executableURL = URL(fileURLWithPath: \"/usr/bin/env\")\n        proc.arguments = [\"pgrep\", \"-f\", regex]\n\n        let stdout = Pipe()\n        proc.standardOutput = stdout\n        proc.standardError = Pipe()\n        proc.standardInput = nil\n\n        do {\n            try proc.run()\n        } catch {\n            return []\n        }\n        proc.waitUntilExit()\n\n        // Exit code 1 = \"no processes matched\".\n        if proc.terminationStatus != 0 { return [] }\n\n        let data = stdout.fileHandleForReading.readDataToEndOfFile()\n        guard let text = String(data: data, encoding: .utf8) else { return [] }\n        return text\n            .split(whereSeparator: \\.isNewline)\n            .compactMap { Int32($0) }\n            .map { pid_t($0) }\n    }\n}\n\nprivate let _registerTestProcessCleanup: Void = TestProcessCleanup.register()\n\n@_cdecl(\"codexbar_test_cleanup_atexit\")\nprivate func _testProcessCleanupAtExit() {\n    TestProcessCleanup.terminateLeakedCodexAppServers()\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/TestStores.swift",
    "content": "import CodexBarCore\nimport Foundation\n@testable import CodexBar\n\nfinal class InMemoryCookieHeaderStore: CookieHeaderStoring, @unchecked Sendable {\n    var value: String?\n\n    init(value: String? = nil) {\n        self.value = value\n    }\n\n    func loadCookieHeader() throws -> String? {\n        self.value\n    }\n\n    func storeCookieHeader(_ header: String?) throws {\n        self.value = header\n    }\n}\n\nfinal class InMemoryMiniMaxCookieStore: MiniMaxCookieStoring, @unchecked Sendable {\n    var value: String?\n\n    init(value: String? = nil) {\n        self.value = value\n    }\n\n    func loadCookieHeader() throws -> String? {\n        self.value\n    }\n\n    func storeCookieHeader(_ header: String?) throws {\n        self.value = header\n    }\n}\n\nfinal class InMemoryMiniMaxAPITokenStore: MiniMaxAPITokenStoring, @unchecked Sendable {\n    var value: String?\n\n    init(value: String? = nil) {\n        self.value = value\n    }\n\n    func loadToken() throws -> String? {\n        self.value\n    }\n\n    func storeToken(_ token: String?) throws {\n        self.value = token\n    }\n}\n\nfinal class InMemoryKimiTokenStore: KimiTokenStoring, @unchecked Sendable {\n    var value: String?\n\n    init(value: String? = nil) {\n        self.value = value\n    }\n\n    func loadToken() throws -> String? {\n        self.value\n    }\n\n    func storeToken(_ token: String?) throws {\n        self.value = token\n    }\n}\n\nfinal class InMemoryKimiK2TokenStore: KimiK2TokenStoring, @unchecked Sendable {\n    var value: String?\n\n    init(value: String? = nil) {\n        self.value = value\n    }\n\n    func loadToken() throws -> String? {\n        self.value\n    }\n\n    func storeToken(_ token: String?) throws {\n        self.value = token\n    }\n}\n\nfinal class InMemoryCopilotTokenStore: CopilotTokenStoring, @unchecked Sendable {\n    var value: String?\n\n    init(value: String? = nil) {\n        self.value = value\n    }\n\n    func loadToken() throws -> String? {\n        self.value\n    }\n\n    func storeToken(_ token: String?) throws {\n        self.value = token\n    }\n}\n\nfinal class InMemoryTokenAccountStore: ProviderTokenAccountStoring, @unchecked Sendable {\n    var accounts: [UsageProvider: ProviderTokenAccountData] = [:]\n    private let fileURL: URL\n\n    init(fileURL: URL = FileManager.default.temporaryDirectory.appendingPathComponent(\n        \"token-accounts-\\(UUID().uuidString).json\"))\n    {\n        self.fileURL = fileURL\n    }\n\n    func loadAccounts() throws -> [UsageProvider: ProviderTokenAccountData] {\n        self.accounts\n    }\n\n    func storeAccounts(_ accounts: [UsageProvider: ProviderTokenAccountData]) throws {\n        self.accounts = accounts\n    }\n\n    func ensureFileExists() throws -> URL {\n        self.fileURL\n    }\n}\n\nfunc testConfigStore(suiteName: String, reset: Bool = true) -> CodexBarConfigStore {\n    let sanitized = suiteName.replacingOccurrences(of: \"/\", with: \"-\")\n    let base = FileManager.default.temporaryDirectory\n        .appendingPathComponent(\"codexbar-tests\", isDirectory: true)\n        .appendingPathComponent(sanitized, isDirectory: true)\n    let url = base.appendingPathComponent(\"config.json\")\n    if reset {\n        try? FileManager.default.removeItem(at: url)\n    }\n    return CodexBarConfigStore(fileURL: url)\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/TextParsingTests.swift",
    "content": "import CodexBarCore\nimport Testing\n\nstruct TextParsingTests {\n    @Test\n    func `strip ANSI codes removes cursor visibility CSI`() {\n        let input = \"\\u{001B}[?25hhello\\u{001B}[0m\"\n        let stripped = TextParsing.stripANSICodes(input)\n        #expect(stripped == \"hello\")\n    }\n\n    @Test\n    func `first number parses decimal separators`() {\n        let dotDecimal = TextParsing.firstNumber(pattern: #\"Credits:\\s*([0-9][0-9., ]*)\"#, text: \"Credits: 54.72\")\n        #expect(dotDecimal == 54.72)\n\n        let commaDecimal = TextParsing.firstNumber(pattern: #\"Credits:\\s*([0-9][0-9., ]*)\"#, text: \"Credits: 54,72\")\n        #expect(commaDecimal == 54.72)\n\n        let mixedCommaDecimal = TextParsing.firstNumber(\n            pattern: #\"Credits:\\s*([0-9][0-9., ]*)\"#,\n            text: \"Credits: 1.234,56\")\n        #expect(mixedCommaDecimal == 1234.56)\n\n        let mixedDotDecimal = TextParsing.firstNumber(\n            pattern: #\"Credits:\\s*([0-9][0-9., ]*)\"#,\n            text: \"Credits: 1,234.56\")\n        #expect(mixedDotDecimal == 1234.56)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/TokenAccountEnvironmentPrecedenceTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n@testable import CodexBar\n@testable import CodexBarCLI\n\n@MainActor\nstruct TokenAccountEnvironmentPrecedenceTests {\n    @Test\n    func `token account environment overrides config API key in app environment builder`() {\n        let settings = Self.makeSettingsStore(suite: \"TokenAccountEnvironmentPrecedenceTests-app\")\n        settings.zaiAPIToken = \"config-token\"\n        settings.addTokenAccount(provider: .zai, label: \"Account 1\", token: \"account-token\")\n\n        let env = ProviderRegistry.makeEnvironment(\n            base: [\"FOO\": \"bar\"],\n            provider: .zai,\n            settings: settings,\n            tokenOverride: nil)\n\n        #expect(env[\"FOO\"] == \"bar\")\n        #expect(env[ZaiSettingsReader.apiTokenKey] == \"account-token\")\n        #expect(env[ZaiSettingsReader.apiTokenKey] != \"config-token\")\n    }\n\n    @Test\n    func `token account environment overrides config API key in CLI environment builder`() throws {\n        let config = CodexBarConfig(\n            providers: [\n                ProviderConfig(id: .zai, apiKey: \"config-token\"),\n            ])\n        let selection = TokenAccountCLISelection(label: nil, index: nil, allAccounts: false)\n        let tokenContext = try TokenAccountCLIContext(selection: selection, config: config, verbose: false)\n        let account = ProviderTokenAccount(\n            id: UUID(),\n            label: \"Account 1\",\n            token: \"account-token\",\n            addedAt: Date().timeIntervalSince1970,\n            lastUsed: nil)\n\n        let env = tokenContext.environment(base: [:], provider: .zai, account: account)\n\n        #expect(env[ZaiSettingsReader.apiTokenKey] == \"account-token\")\n        #expect(env[ZaiSettingsReader.apiTokenKey] != \"config-token\")\n    }\n\n    @Test\n    func `ollama token account selection forces manual cookie source in CLI settings snapshot`() throws {\n        let accounts = ProviderTokenAccountData(\n            version: 1,\n            accounts: [\n                ProviderTokenAccount(\n                    id: UUID(),\n                    label: \"Primary\",\n                    token: \"session=account-token\",\n                    addedAt: 0,\n                    lastUsed: nil),\n            ],\n            activeIndex: 0)\n        let config = CodexBarConfig(\n            providers: [\n                ProviderConfig(\n                    id: .ollama,\n                    cookieSource: .auto,\n                    tokenAccounts: accounts),\n            ])\n        let selection = TokenAccountCLISelection(label: nil, index: nil, allAccounts: false)\n        let tokenContext = try TokenAccountCLIContext(selection: selection, config: config, verbose: false)\n        let account = try #require(tokenContext.resolvedAccounts(for: .ollama).first)\n        let snapshot = try #require(tokenContext.settingsSnapshot(for: .ollama, account: account))\n        let ollamaSettings = try #require(snapshot.ollama)\n\n        #expect(ollamaSettings.cookieSource == .manual)\n        #expect(ollamaSettings.manualCookieHeader == \"session=account-token\")\n    }\n\n    @Test\n    func `claude OAuth token account overrides environment in app environment builder`() {\n        let settings = Self.makeSettingsStore(suite: \"TokenAccountEnvironmentPrecedenceTests-claude-app\")\n        settings.addTokenAccount(provider: .claude, label: \"OAuth\", token: \"Bearer sk-ant-oat-account-token\")\n\n        let env = ProviderRegistry.makeEnvironment(\n            base: [\"FOO\": \"bar\"],\n            provider: .claude,\n            settings: settings,\n            tokenOverride: nil)\n\n        #expect(env[\"FOO\"] == \"bar\")\n        #expect(env[ClaudeOAuthCredentialsStore.environmentTokenKey] == \"sk-ant-oat-account-token\")\n    }\n\n    @Test\n    func `claude OAuth token selection forces OAuth in CLI settings snapshot`() throws {\n        let accounts = ProviderTokenAccountData(\n            version: 1,\n            accounts: [\n                ProviderTokenAccount(\n                    id: UUID(),\n                    label: \"Primary\",\n                    token: \"Bearer sk-ant-oat-account-token\",\n                    addedAt: 0,\n                    lastUsed: nil),\n            ],\n            activeIndex: 0)\n        let config = CodexBarConfig(\n            providers: [\n                ProviderConfig(\n                    id: .claude,\n                    cookieSource: .auto,\n                    tokenAccounts: accounts),\n            ])\n        let selection = TokenAccountCLISelection(label: nil, index: nil, allAccounts: false)\n        let tokenContext = try TokenAccountCLIContext(selection: selection, config: config, verbose: false)\n        let account = try #require(tokenContext.resolvedAccounts(for: .claude).first)\n        let snapshot = try #require(tokenContext.settingsSnapshot(for: .claude, account: account))\n        let claudeSettings = try #require(snapshot.claude)\n\n        #expect(claudeSettings.usageDataSource == .oauth)\n        #expect(claudeSettings.cookieSource == .off)\n        #expect(claudeSettings.manualCookieHeader == nil)\n    }\n\n    @Test\n    func `claude OAuth token selection injects environment override in CLI`() throws {\n        let accounts = ProviderTokenAccountData(\n            version: 1,\n            accounts: [\n                ProviderTokenAccount(\n                    id: UUID(),\n                    label: \"Primary\",\n                    token: \"Bearer sk-ant-oat-account-token\",\n                    addedAt: 0,\n                    lastUsed: nil),\n            ],\n            activeIndex: 0)\n        let config = CodexBarConfig(\n            providers: [\n                ProviderConfig(id: .claude, tokenAccounts: accounts),\n            ])\n        let selection = TokenAccountCLISelection(label: nil, index: nil, allAccounts: false)\n        let tokenContext = try TokenAccountCLIContext(selection: selection, config: config, verbose: false)\n        let account = try #require(tokenContext.resolvedAccounts(for: .claude).first)\n\n        let env = tokenContext.environment(base: [\"FOO\": \"bar\"], provider: .claude, account: account)\n\n        #expect(env[\"FOO\"] == \"bar\")\n        #expect(env[ClaudeOAuthCredentialsStore.environmentTokenKey] == \"sk-ant-oat-account-token\")\n    }\n\n    @Test\n    func `claude OAuth token selection promotes auto source mode in CLI`() throws {\n        let account = ProviderTokenAccount(\n            id: UUID(),\n            label: \"Primary\",\n            token: \"Bearer sk-ant-oat-account-token\",\n            addedAt: 0,\n            lastUsed: nil)\n        let config = CodexBarConfig(providers: [ProviderConfig(id: .claude)])\n        let tokenContext = try TokenAccountCLIContext(\n            selection: TokenAccountCLISelection(label: nil, index: nil, allAccounts: false),\n            config: config,\n            verbose: false)\n\n        let effectiveSourceMode = tokenContext.effectiveSourceMode(\n            base: .auto,\n            provider: .claude,\n            account: account)\n\n        #expect(effectiveSourceMode == .oauth)\n    }\n\n    @Test\n    func `claude session key selection stays in manual cookie mode in CLI settings snapshot`() throws {\n        let accounts = ProviderTokenAccountData(\n            version: 1,\n            accounts: [\n                ProviderTokenAccount(\n                    id: UUID(),\n                    label: \"Primary\",\n                    token: \"sk-ant-session-token\",\n                    addedAt: 0,\n                    lastUsed: nil),\n            ],\n            activeIndex: 0)\n        let config = CodexBarConfig(\n            providers: [\n                ProviderConfig(\n                    id: .claude,\n                    cookieSource: .auto,\n                    tokenAccounts: accounts),\n            ])\n        let selection = TokenAccountCLISelection(label: nil, index: nil, allAccounts: false)\n        let tokenContext = try TokenAccountCLIContext(selection: selection, config: config, verbose: false)\n        let account = try #require(tokenContext.resolvedAccounts(for: .claude).first)\n        let snapshot = try #require(tokenContext.settingsSnapshot(for: .claude, account: account))\n        let claudeSettings = try #require(snapshot.claude)\n\n        #expect(claudeSettings.usageDataSource == .auto)\n        #expect(claudeSettings.cookieSource == .manual)\n        #expect(claudeSettings.manualCookieHeader == \"sessionKey=sk-ant-session-token\")\n    }\n\n    @Test\n    func `claude config manual cookie uses shared route in CLI settings snapshot`() throws {\n        let config = CodexBarConfig(\n            providers: [\n                ProviderConfig(\n                    id: .claude,\n                    cookieHeader: \"Cookie: sessionKey=sk-ant-session-token; foo=bar\"),\n            ])\n        let selection = TokenAccountCLISelection(label: nil, index: nil, allAccounts: false)\n        let tokenContext = try TokenAccountCLIContext(selection: selection, config: config, verbose: false)\n        let snapshot = try #require(tokenContext.settingsSnapshot(for: .claude, account: nil))\n        let claudeSettings = try #require(snapshot.claude)\n\n        #expect(claudeSettings.usageDataSource == .auto)\n        #expect(claudeSettings.cookieSource == .manual)\n        #expect(claudeSettings.manualCookieHeader == \"sessionKey=sk-ant-session-token; foo=bar\")\n    }\n\n    @Test\n    func `claude config manual cookie does not promote auto source mode in CLI`() throws {\n        let config = CodexBarConfig(\n            providers: [\n                ProviderConfig(\n                    id: .claude,\n                    cookieHeader: \"Cookie: sessionKey=sk-ant-session-token\"),\n            ])\n        let tokenContext = try TokenAccountCLIContext(\n            selection: TokenAccountCLISelection(label: nil, index: nil, allAccounts: false),\n            config: config,\n            verbose: false)\n\n        let effectiveSourceMode = tokenContext.effectiveSourceMode(\n            base: .auto,\n            provider: .claude,\n            account: nil)\n\n        #expect(effectiveSourceMode == .auto)\n    }\n\n    @Test\n    func `apply account label in app preserves snapshot fields`() {\n        let settings = Self.makeSettingsStore(suite: \"TokenAccountEnvironmentPrecedenceTests-apply-app\")\n        let store = Self.makeUsageStore(settings: settings)\n        let snapshot = Self.makeSnapshotWithAllFields(provider: .zai)\n        let account = ProviderTokenAccount(\n            id: UUID(),\n            label: \"Team Account\",\n            token: \"account-token\",\n            addedAt: 0,\n            lastUsed: nil)\n\n        let labeled = store.applyAccountLabel(snapshot, provider: .zai, account: account)\n\n        Self.expectSnapshotFieldsPreserved(before: snapshot, after: labeled)\n        #expect(labeled.identity?.providerID == .zai)\n        #expect(labeled.identity?.accountEmail == \"Team Account\")\n    }\n\n    @Test\n    func `apply account label in CLI preserves snapshot fields`() throws {\n        let context = try TokenAccountCLIContext(\n            selection: TokenAccountCLISelection(label: nil, index: nil, allAccounts: false),\n            config: CodexBarConfig(providers: []),\n            verbose: false)\n        let snapshot = Self.makeSnapshotWithAllFields(provider: .zai)\n        let account = ProviderTokenAccount(\n            id: UUID(),\n            label: \"CLI Account\",\n            token: \"account-token\",\n            addedAt: 0,\n            lastUsed: nil)\n\n        let labeled = context.applyAccountLabel(snapshot, provider: .zai, account: account)\n\n        Self.expectSnapshotFieldsPreserved(before: snapshot, after: labeled)\n        #expect(labeled.identity?.providerID == .zai)\n        #expect(labeled.identity?.accountEmail == \"CLI Account\")\n    }\n\n    private static func makeSettingsStore(suite: String) -> SettingsStore {\n        let defaults = UserDefaults(suiteName: suite)!\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n\n        return SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore(),\n            codexCookieStore: InMemoryCookieHeaderStore(),\n            claudeCookieStore: InMemoryCookieHeaderStore(),\n            cursorCookieStore: InMemoryCookieHeaderStore(),\n            opencodeCookieStore: InMemoryCookieHeaderStore(),\n            factoryCookieStore: InMemoryCookieHeaderStore(),\n            minimaxCookieStore: InMemoryMiniMaxCookieStore(),\n            minimaxAPITokenStore: InMemoryMiniMaxAPITokenStore(),\n            kimiTokenStore: InMemoryKimiTokenStore(),\n            kimiK2TokenStore: InMemoryKimiK2TokenStore(),\n            augmentCookieStore: InMemoryCookieHeaderStore(),\n            ampCookieStore: InMemoryCookieHeaderStore(),\n            copilotTokenStore: InMemoryCopilotTokenStore(),\n            tokenAccountStore: InMemoryTokenAccountStore())\n    }\n\n    private static func makeUsageStore(settings: SettingsStore) -> UsageStore {\n        UsageStore(\n            fetcher: UsageFetcher(environment: [:]),\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            settings: settings)\n    }\n\n    private static func makeSnapshotWithAllFields(provider: UsageProvider) -> UsageSnapshot {\n        let now = Date(timeIntervalSince1970: 1_700_000_000)\n        let reset = Date(timeIntervalSince1970: 1_700_003_600)\n        let tokenLimit = ZaiLimitEntry(\n            type: .tokensLimit,\n            unit: .hours,\n            number: 6,\n            usage: 200,\n            currentValue: 40,\n            remaining: 160,\n            percentage: 20,\n            usageDetails: [ZaiUsageDetail(modelCode: \"glm-4\", usage: 40)],\n            nextResetTime: reset)\n        let identity = ProviderIdentitySnapshot(\n            providerID: provider,\n            accountEmail: nil,\n            accountOrganization: \"Org\",\n            loginMethod: \"Pro\")\n\n        return UsageSnapshot(\n            primary: RateWindow(usedPercent: 21, windowMinutes: 60, resetsAt: reset, resetDescription: \"primary\"),\n            secondary: RateWindow(usedPercent: 42, windowMinutes: 1440, resetsAt: nil, resetDescription: \"secondary\"),\n            tertiary: RateWindow(usedPercent: 7, windowMinutes: nil, resetsAt: nil, resetDescription: \"tertiary\"),\n            providerCost: ProviderCostSnapshot(\n                used: 12.5,\n                limit: 25,\n                currencyCode: \"USD\",\n                period: \"Monthly\",\n                resetsAt: reset,\n                updatedAt: now),\n            zaiUsage: ZaiUsageSnapshot(\n                tokenLimit: tokenLimit,\n                timeLimit: nil,\n                planName: \"Z.ai Pro\",\n                updatedAt: now),\n            minimaxUsage: MiniMaxUsageSnapshot(\n                planName: \"MiniMax\",\n                availablePrompts: 500,\n                currentPrompts: 120,\n                remainingPrompts: 380,\n                windowMinutes: 1440,\n                usedPercent: 24,\n                resetsAt: reset,\n                updatedAt: now),\n            openRouterUsage: OpenRouterUsageSnapshot(\n                totalCredits: 50,\n                totalUsage: 10,\n                balance: 40,\n                usedPercent: 20,\n                rateLimit: nil,\n                updatedAt: now),\n            cursorRequests: CursorRequestUsage(used: 7, limit: 70),\n            updatedAt: now,\n            identity: identity)\n    }\n\n    private static func expectSnapshotFieldsPreserved(before: UsageSnapshot, after: UsageSnapshot) {\n        #expect(after.primary?.usedPercent == before.primary?.usedPercent)\n        #expect(after.secondary?.usedPercent == before.secondary?.usedPercent)\n        #expect(after.tertiary?.usedPercent == before.tertiary?.usedPercent)\n        #expect(after.providerCost?.used == before.providerCost?.used)\n        #expect(after.providerCost?.limit == before.providerCost?.limit)\n        #expect(after.providerCost?.currencyCode == before.providerCost?.currencyCode)\n        #expect(after.zaiUsage?.planName == before.zaiUsage?.planName)\n        #expect(after.zaiUsage?.tokenLimit?.usage == before.zaiUsage?.tokenLimit?.usage)\n        #expect(after.minimaxUsage?.planName == before.minimaxUsage?.planName)\n        #expect(after.minimaxUsage?.availablePrompts == before.minimaxUsage?.availablePrompts)\n        #expect(after.openRouterUsage?.balance == before.openRouterUsage?.balance)\n        #expect(after.openRouterUsage?.rateLimit?.requests == before.openRouterUsage?.rateLimit?.requests)\n        #expect(after.cursorRequests?.used == before.cursorRequests?.used)\n        #expect(after.cursorRequests?.limit == before.cursorRequests?.limit)\n        #expect(after.updatedAt == before.updatedAt)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/TokenAccountStoreTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n@testable import CodexBar\n\n@Test\nfunc `ProviderTokenAccountData encoding`() throws {\n    let now = Date().timeIntervalSince1970\n    let account = ProviderTokenAccount(\n        id: UUID(),\n        label: \"user@example.com\",\n        token: \"test-token\",\n        addedAt: now,\n        lastUsed: now)\n    let data = ProviderTokenAccountData(version: 1, accounts: [account], activeIndex: 0)\n\n    let encoder = JSONEncoder()\n    let encoded = try encoder.encode(data)\n\n    let decoder = JSONDecoder()\n    let decoded = try decoder.decode(ProviderTokenAccountData.self, from: encoded)\n\n    #expect(decoded.version == 1)\n    #expect(decoded.accounts.count == 1)\n    #expect(decoded.accounts[0].label == \"user@example.com\")\n    #expect(decoded.activeIndex == 0)\n}\n\n@Test\nfunc `FileTokenAccountStore round trip`() throws {\n    let tempDir = FileManager.default.temporaryDirectory\n    let fileURL = tempDir.appendingPathComponent(\"codexbar-token-accounts-test.json\")\n    defer { try? FileManager.default.removeItem(at: fileURL) }\n\n    let now = Date().timeIntervalSince1970\n    let account = ProviderTokenAccount(\n        id: UUID(),\n        label: \"user@example.com\",\n        token: \"test-token\",\n        addedAt: now,\n        lastUsed: nil)\n    let data = ProviderTokenAccountData(version: 1, accounts: [account], activeIndex: 0)\n    let store = FileTokenAccountStore(fileURL: fileURL)\n\n    try store.storeAccounts([.claude: data])\n    let loaded = try store.loadAccounts()\n\n    #expect(loaded[.claude]?.accounts.count == 1)\n    #expect(loaded[.claude]?.accounts[0].label == \"user@example.com\")\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/UpdateChannelTests.swift",
    "content": "import Testing\n@testable import CodexBar\n\nstruct UpdateChannelTests {\n    @Test\n    func `default channel from stable version`() {\n        #expect(UpdateChannel.defaultChannel(for: \"1.2.3\") == .stable)\n    }\n\n    @Test\n    func `default channel from prerelease version`() {\n        #expect(UpdateChannel.defaultChannel(for: \"1.2.3-beta.1\") == .beta)\n        #expect(UpdateChannel.defaultChannel(for: \"1.2.3-rc.1\") == .beta)\n        #expect(UpdateChannel.defaultChannel(for: \"1.2.3-alpha\") == .beta)\n    }\n\n    @Test\n    func `allowed sparkle channels`() {\n        #expect(UpdateChannel.stable.allowedSparkleChannels == [\"\"])\n        #expect(UpdateChannel.beta.allowedSparkleChannels == [\"\", UpdateChannel.sparkleBetaChannel])\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/UsageFormatterTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n@testable import CodexBar\n\nstruct UsageFormatterTests {\n    @Test\n    func `formats usage line`() {\n        let line = UsageFormatter.usageLine(remaining: 25, used: 75, showUsed: false)\n        #expect(line == \"25% left\")\n    }\n\n    @Test\n    func `formats usage line show used`() {\n        let line = UsageFormatter.usageLine(remaining: 25, used: 75, showUsed: true)\n        #expect(line == \"75% used\")\n    }\n\n    @Test\n    func `relative updated recent`() {\n        let now = Date()\n        let fiveHoursAgo = now.addingTimeInterval(-5 * 3600)\n        let text = UsageFormatter.updatedString(from: fiveHoursAgo, now: now)\n        #expect(text.contains(\"Updated\"))\n        // Check for relative time format (varies by locale: \"ago\" in English, \"전\" in Korean, etc.)\n        #expect(text.contains(\"5\") || text.lowercased().contains(\"hour\") || text.contains(\"시간\"))\n    }\n\n    @Test\n    func `absolute updated old`() {\n        let now = Date()\n        let dayAgo = now.addingTimeInterval(-26 * 3600)\n        let text = UsageFormatter.updatedString(from: dayAgo, now: now)\n        #expect(text.contains(\"Updated\"))\n        #expect(!text.contains(\"ago\"))\n    }\n\n    @Test\n    func `reset countdown minutes`() {\n        let now = Date(timeIntervalSince1970: 1_000_000)\n        let reset = now.addingTimeInterval(10 * 60 + 1)\n        #expect(UsageFormatter.resetCountdownDescription(from: reset, now: now) == \"in 11m\")\n    }\n\n    @Test\n    func `reset countdown hours and minutes`() {\n        let now = Date(timeIntervalSince1970: 1_000_000)\n        let reset = now.addingTimeInterval(3 * 3600 + 31 * 60)\n        #expect(UsageFormatter.resetCountdownDescription(from: reset, now: now) == \"in 3h 31m\")\n    }\n\n    @Test\n    func `reset countdown days and hours`() {\n        let now = Date(timeIntervalSince1970: 1_000_000)\n        let reset = now.addingTimeInterval((26 * 3600) + 10)\n        #expect(UsageFormatter.resetCountdownDescription(from: reset, now: now) == \"in 1d 2h\")\n    }\n\n    @Test\n    func `reset countdown exact hour`() {\n        let now = Date(timeIntervalSince1970: 1_000_000)\n        let reset = now.addingTimeInterval(60 * 60)\n        #expect(UsageFormatter.resetCountdownDescription(from: reset, now: now) == \"in 1h\")\n    }\n\n    @Test\n    func `reset countdown past date`() {\n        let now = Date(timeIntervalSince1970: 1_000_000)\n        let reset = now.addingTimeInterval(-10)\n        #expect(UsageFormatter.resetCountdownDescription(from: reset, now: now) == \"now\")\n    }\n\n    @Test\n    func `reset line uses countdown when resets at is available`() {\n        let now = Date(timeIntervalSince1970: 1_000_000)\n        let reset = now.addingTimeInterval(10 * 60 + 1)\n        let window = RateWindow(usedPercent: 0, windowMinutes: nil, resetsAt: reset, resetDescription: \"Resets soon\")\n        let text = UsageFormatter.resetLine(for: window, style: .countdown, now: now)\n        #expect(text == \"Resets in 11m\")\n    }\n\n    @Test\n    func `reset line falls back to provided description`() {\n        let window = RateWindow(\n            usedPercent: 0,\n            windowMinutes: nil,\n            resetsAt: nil,\n            resetDescription: \"Resets at 23:30 (UTC)\")\n        let countdown = UsageFormatter.resetLine(for: window, style: .countdown)\n        let absolute = UsageFormatter.resetLine(for: window, style: .absolute)\n        #expect(countdown == \"Resets at 23:30 (UTC)\")\n        #expect(absolute == \"Resets at 23:30 (UTC)\")\n    }\n\n    @Test\n    func `model display name strips trailing dates`() {\n        #expect(UsageFormatter.modelDisplayName(\"claude-opus-4-5-20251101\") == \"claude-opus-4-5\")\n        #expect(UsageFormatter.modelDisplayName(\"gpt-4o-2024-08-06\") == \"gpt-4o\")\n        #expect(UsageFormatter.modelDisplayName(\"Claude Opus 4.5 2025 1101\") == \"Claude Opus 4.5\")\n        #expect(UsageFormatter.modelDisplayName(\"claude-sonnet-4-5\") == \"claude-sonnet-4-5\")\n        #expect(UsageFormatter.modelDisplayName(\"gpt-5.3-codex-spark\") == \"gpt-5.3-codex-spark\")\n    }\n\n    @Test\n    func `model cost detail uses research preview label`() {\n        #expect(\n            UsageFormatter.modelCostDetail(\"gpt-5.3-codex-spark\", costUSD: 0, totalTokens: nil) == \"Research Preview\")\n        #expect(UsageFormatter.modelCostDetail(\"gpt-5.2-codex\", costUSD: 0.42, totalTokens: nil) == \"$0.42\")\n    }\n\n    @Test\n    func `model cost detail includes token counts when present`() {\n        #expect(UsageFormatter.modelCostDetail(\"gpt-5.2-codex\", costUSD: 0.42, totalTokens: 1200) == \"$0.42 · 1.2K\")\n        #expect(\n            UsageFormatter.modelCostDetail(\"gpt-5.3-codex-spark\", costUSD: 0, totalTokens: 1500)\n                == \"Research Preview · 1.5K\")\n        #expect(UsageFormatter.modelCostDetail(\"custom-model\", costUSD: nil, totalTokens: 987) == \"987\")\n    }\n\n    @Test\n    func `clean plan maps O auth to ollama`() {\n        #expect(UsageFormatter.cleanPlanName(\"oauth\") == \"Ollama\")\n    }\n\n    // MARK: - Currency Formatting\n\n    @Test\n    func `currency string formats USD correctly`() {\n        // Should produce \"$54.72\" without space after symbol\n        let result = UsageFormatter.currencyString(54.72, currencyCode: \"USD\")\n        #expect(result == \"$54.72\")\n        #expect(!result.contains(\"$ \")) // No space after symbol\n    }\n\n    @Test\n    func `currency string handles large values`() {\n        let result = UsageFormatter.currencyString(1234.56, currencyCode: \"USD\")\n        // For USD, we use direct string formatting with thousand separators\n        #expect(result == \"$1,234.56\")\n        #expect(!result.contains(\"$ \")) // No space after symbol\n    }\n\n    @Test\n    func `currency string handles very large values`() {\n        let result = UsageFormatter.currencyString(1_234_567.89, currencyCode: \"USD\")\n        #expect(result == \"$1,234,567.89\")\n    }\n\n    @Test\n    func `currency string handles negative values`() {\n        // Negative sign should come before the dollar sign: -$54.72 (not $-54.72)\n        let result = UsageFormatter.currencyString(-54.72, currencyCode: \"USD\")\n        #expect(result == \"-$54.72\")\n    }\n\n    @Test\n    func `currency string handles negative large values`() {\n        let result = UsageFormatter.currencyString(-1234.56, currencyCode: \"USD\")\n        #expect(result == \"-$1,234.56\")\n    }\n\n    @Test\n    func `usd string matches currency string`() {\n        // usdString should produce identical output to currencyString for USD\n        #expect(UsageFormatter.usdString(54.72) == UsageFormatter.currencyString(54.72, currencyCode: \"USD\"))\n        #expect(UsageFormatter.usdString(-1234.56) == UsageFormatter.currencyString(-1234.56, currencyCode: \"USD\"))\n        #expect(UsageFormatter.usdString(0) == UsageFormatter.currencyString(0, currencyCode: \"USD\"))\n    }\n\n    @Test\n    func `currency string handles zero`() {\n        let result = UsageFormatter.currencyString(0, currencyCode: \"USD\")\n        #expect(result == \"$0.00\")\n    }\n\n    @Test\n    func `currency string handles non USD currencies`() {\n        // FormatStyle handles all currencies with proper symbols\n        let eur = UsageFormatter.currencyString(54.72, currencyCode: \"EUR\")\n        #expect(eur == \"€54.72\")\n\n        let gbp = UsageFormatter.currencyString(54.72, currencyCode: \"GBP\")\n        #expect(gbp == \"£54.72\")\n\n        // Negative non-USD\n        let negEur = UsageFormatter.currencyString(-1234.56, currencyCode: \"EUR\")\n        #expect(negEur == \"-€1,234.56\")\n    }\n\n    @Test\n    func `currency string handles small values`() {\n        // Values smaller than 0.01 should round to $0.00\n        let tiny = UsageFormatter.currencyString(0.001, currencyCode: \"USD\")\n        #expect(tiny == \"$0.00\")\n\n        // Values at 0.005 should round to $0.01 (banker's rounding)\n        let halfCent = UsageFormatter.currencyString(0.005, currencyCode: \"USD\")\n        #expect(halfCent == \"$0.00\" || halfCent == \"$0.01\") // Rounding behavior may vary\n\n        // One cent\n        let oneCent = UsageFormatter.currencyString(0.01, currencyCode: \"USD\")\n        #expect(oneCent == \"$0.01\")\n    }\n\n    @Test\n    func `currency string handles boundary values`() {\n        // Just under 1000 (no comma)\n        let under1k = UsageFormatter.currencyString(999.99, currencyCode: \"USD\")\n        #expect(under1k == \"$999.99\")\n\n        // Exactly 1000 (first comma)\n        let exact1k = UsageFormatter.currencyString(1000.00, currencyCode: \"USD\")\n        #expect(exact1k == \"$1,000.00\")\n\n        // Just over 1000\n        let over1k = UsageFormatter.currencyString(1000.01, currencyCode: \"USD\")\n        #expect(over1k == \"$1,000.01\")\n    }\n\n    @Test\n    func `credits string formats correctly`() {\n        let result = UsageFormatter.creditsString(from: 42.5)\n        #expect(result == \"42.5 left\")\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/UsagePaceTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n\nstruct UsagePaceTests {\n    @Test\n    func `weekly pace computes delta and eta`() {\n        let now = Date(timeIntervalSince1970: 0)\n        let window = RateWindow(\n            usedPercent: 50,\n            windowMinutes: 10080,\n            resetsAt: now.addingTimeInterval(4 * 24 * 3600),\n            resetDescription: nil)\n\n        let pace = UsagePace.weekly(window: window, now: now)\n\n        #expect(pace != nil)\n        guard let pace else { return }\n        #expect(abs(pace.expectedUsedPercent - 42.857) < 0.01)\n        #expect(abs(pace.deltaPercent - 7.143) < 0.01)\n        #expect(pace.stage == .ahead)\n        #expect(pace.willLastToReset == false)\n        #expect(pace.etaSeconds != nil)\n        #expect(pace.runOutProbability == nil)\n        #expect(abs((pace.etaSeconds ?? 0) - (3 * 24 * 3600)) < 1)\n    }\n\n    @Test\n    func `weekly pace marks lasts to reset when usage is low`() {\n        let now = Date(timeIntervalSince1970: 0)\n        let window = RateWindow(\n            usedPercent: 5,\n            windowMinutes: 10080,\n            resetsAt: now.addingTimeInterval(4 * 24 * 3600),\n            resetDescription: nil)\n\n        let pace = UsagePace.weekly(window: window, now: now)\n\n        #expect(pace != nil)\n        guard let pace else { return }\n        #expect(pace.willLastToReset == true)\n        #expect(pace.etaSeconds == nil)\n        #expect(pace.runOutProbability == nil)\n        #expect(pace.stage == .farBehind)\n    }\n\n    @Test\n    func `weekly pace hides when reset missing or outside window`() {\n        let now = Date(timeIntervalSince1970: 0)\n        let missing = RateWindow(\n            usedPercent: 10,\n            windowMinutes: 10080,\n            resetsAt: nil,\n            resetDescription: nil)\n        let tooFar = RateWindow(\n            usedPercent: 10,\n            windowMinutes: 10080,\n            resetsAt: now.addingTimeInterval(9 * 24 * 3600),\n            resetDescription: nil)\n\n        #expect(UsagePace.weekly(window: missing, now: now) == nil)\n        #expect(UsagePace.weekly(window: tooFar, now: now) == nil)\n    }\n\n    @Test\n    func `weekly pace hides when usage exists but no elapsed`() {\n        let now = Date(timeIntervalSince1970: 0)\n        let window = RateWindow(\n            usedPercent: 12,\n            windowMinutes: 10080,\n            resetsAt: now.addingTimeInterval(7 * 24 * 3600),\n            resetDescription: nil)\n\n        let pace = UsagePace.weekly(window: window, now: now)\n\n        #expect(pace == nil)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/UsagePaceTextTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n@testable import CodexBar\n\nstruct UsagePaceTextTests {\n    @Test\n    func `weekly pace detail provides left right labels`() throws {\n        let now = Date(timeIntervalSince1970: 0)\n        let window = RateWindow(\n            usedPercent: 50,\n            windowMinutes: 10080,\n            resetsAt: now.addingTimeInterval(4 * 24 * 3600),\n            resetDescription: nil)\n        let pace = try #require(UsagePace.weekly(window: window, now: now))\n\n        let detail = UsagePaceText.weeklyDetail(pace: pace, now: now)\n\n        #expect(detail.leftLabel == \"7% in deficit\")\n        #expect(detail.rightLabel == \"Runs out in 3d\")\n    }\n\n    @Test\n    func `weekly pace detail reports lasts until reset`() throws {\n        let now = Date(timeIntervalSince1970: 0)\n        let window = RateWindow(\n            usedPercent: 10,\n            windowMinutes: 10080,\n            resetsAt: now.addingTimeInterval(4 * 24 * 3600),\n            resetDescription: nil)\n        let pace = try #require(UsagePace.weekly(window: window, now: now))\n\n        let detail = UsagePaceText.weeklyDetail(pace: pace, now: now)\n\n        #expect(detail.leftLabel == \"33% in reserve\")\n        #expect(detail.rightLabel == \"Lasts until reset\")\n    }\n\n    @Test\n    func `weekly pace summary formats single line text`() throws {\n        let now = Date(timeIntervalSince1970: 0)\n        let window = RateWindow(\n            usedPercent: 50,\n            windowMinutes: 10080,\n            resetsAt: now.addingTimeInterval(4 * 24 * 3600),\n            resetDescription: nil)\n        let pace = try #require(UsagePace.weekly(window: window, now: now))\n\n        let summary = UsagePaceText.weeklySummary(pace: pace, now: now)\n\n        #expect(summary == \"Pace: 7% in deficit · Runs out in 3d\")\n    }\n\n    @Test\n    func `weekly pace detail formats rounded risk when available`() {\n        let now = Date(timeIntervalSince1970: 0)\n        let pace = UsagePace(\n            stage: .ahead,\n            deltaPercent: 8,\n            expectedUsedPercent: 42,\n            actualUsedPercent: 50,\n            etaSeconds: 2 * 24 * 3600,\n            willLastToReset: false,\n            runOutProbability: 0.683)\n\n        let detail = UsagePaceText.weeklyDetail(pace: pace, now: now)\n\n        #expect(detail.rightLabel == \"Runs out in 2d · ≈ 70% run-out risk\")\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/UsageStoreCoverageTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n@testable import CodexBar\n\n@MainActor\nstruct UsageStoreCoverageTests {\n    @Test\n    func `provider with highest usage and icon style`() throws {\n        let settings = Self.makeSettingsStore(suite: \"UsageStoreCoverageTests-highest\")\n        let store = Self.makeUsageStore(settings: settings)\n        let metadata = ProviderRegistry.shared.metadata\n\n        try settings.setProviderEnabled(provider: .codex, metadata: #require(metadata[.codex]), enabled: true)\n        try settings.setProviderEnabled(provider: .factory, metadata: #require(metadata[.factory]), enabled: true)\n        try settings.setProviderEnabled(provider: .claude, metadata: #require(metadata[.claude]), enabled: true)\n\n        let now = Date()\n        store._setSnapshotForTesting(\n            UsageSnapshot(\n                primary: RateWindow(usedPercent: 50, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n                secondary: nil,\n                updatedAt: now),\n            provider: .codex)\n        store._setSnapshotForTesting(\n            UsageSnapshot(\n                primary: RateWindow(usedPercent: 10, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n                secondary: RateWindow(usedPercent: 70, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n                updatedAt: now),\n            provider: .factory)\n        store._setSnapshotForTesting(\n            UsageSnapshot(\n                primary: RateWindow(usedPercent: 100, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n                secondary: nil,\n                updatedAt: now),\n            provider: .claude)\n\n        let highest = store.providerWithHighestUsage()\n        #expect(highest?.provider == .factory)\n        #expect(highest?.usedPercent == 70)\n        #expect(store.iconStyle == .combined)\n\n        try settings.setProviderEnabled(provider: .factory, metadata: #require(metadata[.factory]), enabled: false)\n        try settings.setProviderEnabled(provider: .claude, metadata: #require(metadata[.claude]), enabled: false)\n        #expect(store.iconStyle == store.style(for: .codex))\n\n        store._setErrorForTesting(\"error\", provider: .codex)\n        #expect(store.isStale)\n    }\n\n    @Test\n    func `source label adds open AI web`() {\n        let settings = Self.makeSettingsStore(suite: \"UsageStoreCoverageTests-source\")\n        settings.debugDisableKeychainAccess = false\n        settings.codexUsageDataSource = .oauth\n        settings.codexCookieSource = .manual\n\n        let store = Self.makeUsageStore(settings: settings)\n        store.openAIDashboard = OpenAIDashboardSnapshot(\n            signedInEmail: \"user@example.com\",\n            codeReviewRemainingPercent: nil,\n            creditEvents: [],\n            dailyBreakdown: [],\n            usageBreakdown: [],\n            creditsPurchaseURL: nil,\n            updatedAt: Date())\n        store.openAIDashboardRequiresLogin = false\n\n        let label = store.sourceLabel(for: .codex)\n        #expect(label.contains(\"openai-web\"))\n    }\n\n    @Test\n    func `source label uses configured kilo source`() {\n        let settings = Self.makeSettingsStore(suite: \"UsageStoreCoverageTests-kilo-source\")\n        settings.kiloUsageDataSource = .api\n\n        let store = Self.makeUsageStore(settings: settings)\n        #expect(store.sourceLabel(for: .kilo) == \"api\")\n    }\n\n    @Test\n    func `provider with highest usage prefers kimi rate limit window`() throws {\n        let settings = Self.makeSettingsStore(suite: \"UsageStoreCoverageTests-kimi-highest\")\n        let store = Self.makeUsageStore(settings: settings)\n        let metadata = ProviderRegistry.shared.metadata\n\n        try settings.setProviderEnabled(provider: .codex, metadata: #require(metadata[.codex]), enabled: true)\n        try settings.setProviderEnabled(provider: .kimi, metadata: #require(metadata[.kimi]), enabled: true)\n\n        let now = Date()\n        store._setSnapshotForTesting(\n            UsageSnapshot(\n                primary: RateWindow(usedPercent: 60, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n                secondary: nil,\n                updatedAt: now),\n            provider: .codex)\n        store._setSnapshotForTesting(\n            UsageSnapshot(\n                primary: RateWindow(usedPercent: 10, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n                secondary: RateWindow(usedPercent: 80, windowMinutes: 300, resetsAt: nil, resetDescription: nil),\n                updatedAt: now),\n            provider: .kimi)\n\n        let highest = store.providerWithHighestUsage()\n        #expect(highest?.provider == .kimi)\n        #expect(highest?.usedPercent == 80)\n    }\n\n    @Test\n    func `provider availability and subscription detection`() {\n        let zaiStore = InMemoryZaiTokenStore(value: \"zai-token\")\n        let syntheticStore = InMemorySyntheticTokenStore(value: \"synthetic-token\")\n        let settings = Self.makeSettingsStore(\n            suite: \"UsageStoreCoverageTests-availability\",\n            zaiTokenStore: zaiStore,\n            syntheticTokenStore: syntheticStore)\n        let store = Self.makeUsageStore(settings: settings)\n\n        #expect(store.isProviderAvailable(.zai))\n        #expect(store.isProviderAvailable(.synthetic))\n\n        let identity = ProviderIdentitySnapshot(\n            providerID: .claude,\n            accountEmail: nil,\n            accountOrganization: nil,\n            loginMethod: \"Pro\")\n        store._setSnapshotForTesting(\n            UsageSnapshot(primary: nil, secondary: nil, updatedAt: Date(), identity: identity),\n            provider: .claude)\n        #expect(store.isClaudeSubscription())\n        #expect(UsageStore.isSubscriptionPlan(\"Team\"))\n        #expect(!UsageStore.isSubscriptionPlan(\"api\"))\n    }\n\n    @Test\n    func `status indicators and failure gate`() {\n        #expect(!ProviderStatusIndicator.none.hasIssue)\n        #expect(ProviderStatusIndicator.maintenance.hasIssue)\n        #expect(ProviderStatusIndicator.unknown.label == \"Status unknown\")\n\n        var gate = ConsecutiveFailureGate()\n        let first = gate.shouldSurfaceError(onFailureWithPriorData: true)\n        #expect(!first)\n        let second = gate.shouldSurfaceError(onFailureWithPriorData: true)\n        #expect(second)\n        gate.recordSuccess()\n        let third = gate.shouldSurfaceError(onFailureWithPriorData: false)\n        #expect(third)\n        gate.reset()\n        #expect(gate.streak == 0)\n    }\n\n    private static func makeSettingsStore(\n        suite: String,\n        zaiTokenStore: any ZaiTokenStoring = NoopZaiTokenStore(),\n        syntheticTokenStore: any SyntheticTokenStoring = NoopSyntheticTokenStore())\n        -> SettingsStore\n    {\n        let defaults = UserDefaults(suiteName: suite)!\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n\n        return SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: zaiTokenStore,\n            syntheticTokenStore: syntheticTokenStore,\n            codexCookieStore: InMemoryCookieHeaderStore(),\n            claudeCookieStore: InMemoryCookieHeaderStore(),\n            cursorCookieStore: InMemoryCookieHeaderStore(),\n            opencodeCookieStore: InMemoryCookieHeaderStore(),\n            factoryCookieStore: InMemoryCookieHeaderStore(),\n            minimaxCookieStore: InMemoryMiniMaxCookieStore(),\n            minimaxAPITokenStore: InMemoryMiniMaxAPITokenStore(),\n            kimiTokenStore: InMemoryKimiTokenStore(),\n            kimiK2TokenStore: InMemoryKimiK2TokenStore(),\n            augmentCookieStore: InMemoryCookieHeaderStore(),\n            ampCookieStore: InMemoryCookieHeaderStore(),\n            copilotTokenStore: InMemoryCopilotTokenStore(),\n            tokenAccountStore: InMemoryTokenAccountStore())\n    }\n\n    private static func makeUsageStore(settings: SettingsStore) -> UsageStore {\n        UsageStore(\n            fetcher: UsageFetcher(environment: [:]),\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            settings: settings)\n    }\n}\n\nprivate final class InMemoryZaiTokenStore: ZaiTokenStoring, @unchecked Sendable {\n    var value: String?\n\n    init(value: String? = nil) {\n        self.value = value\n    }\n\n    func loadToken() throws -> String? {\n        self.value\n    }\n\n    func storeToken(_ token: String?) throws {\n        self.value = token\n    }\n}\n\nprivate final class InMemorySyntheticTokenStore: SyntheticTokenStoring, @unchecked Sendable {\n    var value: String?\n\n    init(value: String? = nil) {\n        self.value = value\n    }\n\n    func loadToken() throws -> String? {\n        self.value\n    }\n\n    func storeToken(_ token: String?) throws {\n        self.value = token\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/UsageStoreHighestUsageTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n@testable import CodexBar\n\n@MainActor\nstruct UsageStoreHighestUsageTests {\n    @Test\n    func `selects highest usage among enabled providers`() {\n        let settings = SettingsStore(\n            configStore: testConfigStore(suiteName: \"UsageStoreHighestUsageTests-selects\"),\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n        settings.refreshFrequency = .manual\n        settings.statusChecksEnabled = false\n\n        let registry = ProviderRegistry.shared\n        if let codexMeta = registry.metadata[.codex] {\n            settings.setProviderEnabled(provider: .codex, metadata: codexMeta, enabled: true)\n        }\n        if let claudeMeta = registry.metadata[.claude] {\n            settings.setProviderEnabled(provider: .claude, metadata: claudeMeta, enabled: true)\n        }\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n\n        let codexSnapshot = UsageSnapshot(\n            primary: RateWindow(usedPercent: 25, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            secondary: nil,\n            updatedAt: Date())\n        let claudeSnapshot = UsageSnapshot(\n            primary: RateWindow(usedPercent: 60, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            secondary: nil,\n            updatedAt: Date())\n\n        store._setSnapshotForTesting(codexSnapshot, provider: .codex)\n        store._setSnapshotForTesting(claudeSnapshot, provider: .claude)\n\n        let highest = store.providerWithHighestUsage()\n        #expect(highest?.provider == .claude)\n        #expect(highest?.usedPercent == 60)\n    }\n\n    @Test\n    func `skips fully used providers`() {\n        let settings = SettingsStore(\n            configStore: testConfigStore(suiteName: \"UsageStoreHighestUsageTests-skips\"),\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n        settings.refreshFrequency = .manual\n        settings.statusChecksEnabled = false\n\n        let registry = ProviderRegistry.shared\n        if let codexMeta = registry.metadata[.codex] {\n            settings.setProviderEnabled(provider: .codex, metadata: codexMeta, enabled: true)\n        }\n        if let claudeMeta = registry.metadata[.claude] {\n            settings.setProviderEnabled(provider: .claude, metadata: claudeMeta, enabled: true)\n        }\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n\n        let codexSnapshot = UsageSnapshot(\n            primary: RateWindow(usedPercent: 100, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            secondary: nil,\n            updatedAt: Date())\n        let claudeSnapshot = UsageSnapshot(\n            primary: RateWindow(usedPercent: 80, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            secondary: nil,\n            updatedAt: Date())\n\n        store._setSnapshotForTesting(codexSnapshot, provider: .codex)\n        store._setSnapshotForTesting(claudeSnapshot, provider: .claude)\n\n        let highest = store.providerWithHighestUsage()\n        #expect(highest?.provider == .claude)\n        #expect(highest?.usedPercent == 80)\n    }\n\n    @Test\n    func `automatic metric uses secondary for kimi when ranking highest usage`() {\n        let settings = SettingsStore(\n            configStore: testConfigStore(suiteName: \"UsageStoreHighestUsageTests-kimi-automatic\"),\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n        settings.refreshFrequency = .manual\n        settings.statusChecksEnabled = false\n        settings.setMenuBarMetricPreference(.automatic, for: .kimi)\n\n        let registry = ProviderRegistry.shared\n        if let codexMeta = registry.metadata[.codex] {\n            settings.setProviderEnabled(provider: .codex, metadata: codexMeta, enabled: true)\n        }\n        if let kimiMeta = registry.metadata[.kimi] {\n            settings.setProviderEnabled(provider: .kimi, metadata: kimiMeta, enabled: true)\n        }\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n\n        let codexSnapshot = UsageSnapshot(\n            primary: RateWindow(usedPercent: 70, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            secondary: nil,\n            updatedAt: Date())\n        let kimiSnapshot = UsageSnapshot(\n            primary: RateWindow(usedPercent: 90, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            secondary: RateWindow(usedPercent: 20, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            updatedAt: Date())\n\n        store._setSnapshotForTesting(codexSnapshot, provider: .codex)\n        store._setSnapshotForTesting(kimiSnapshot, provider: .kimi)\n\n        let highest = store.providerWithHighestUsage()\n        #expect(highest?.provider == .codex)\n        #expect(highest?.usedPercent == 70)\n    }\n\n    @Test\n    func `automatic metric keeps copilot most constrained ranking`() {\n        let settings = SettingsStore(\n            configStore: testConfigStore(suiteName: \"UsageStoreHighestUsageTests-copilot-automatic\"),\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n        settings.refreshFrequency = .manual\n        settings.statusChecksEnabled = false\n        settings.setMenuBarMetricPreference(.automatic, for: .copilot)\n\n        let registry = ProviderRegistry.shared\n        if let codexMeta = registry.metadata[.codex] {\n            settings.setProviderEnabled(provider: .codex, metadata: codexMeta, enabled: true)\n        }\n        if let copilotMeta = registry.metadata[.copilot] {\n            settings.setProviderEnabled(provider: .copilot, metadata: copilotMeta, enabled: true)\n        }\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n\n        let codexSnapshot = UsageSnapshot(\n            primary: RateWindow(usedPercent: 70, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            secondary: nil,\n            updatedAt: Date())\n        let copilotSnapshot = UsageSnapshot(\n            primary: RateWindow(usedPercent: 20, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            secondary: RateWindow(usedPercent: 80, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            updatedAt: Date())\n\n        store._setSnapshotForTesting(codexSnapshot, provider: .codex)\n        store._setSnapshotForTesting(copilotSnapshot, provider: .copilot)\n\n        let highest = store.providerWithHighestUsage()\n        #expect(highest?.provider == .copilot)\n        #expect(highest?.usedPercent == 80)\n    }\n\n    @Test\n    func `automatic metric does not exclude partially available copilot at hundred percent`() {\n        let settings = SettingsStore(\n            configStore: testConfigStore(suiteName: \"UsageStoreHighestUsageTests-copilot-partial-100\"),\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n        settings.refreshFrequency = .manual\n        settings.statusChecksEnabled = false\n        settings.setMenuBarMetricPreference(.automatic, for: .copilot)\n\n        let registry = ProviderRegistry.shared\n        if let codexMeta = registry.metadata[.codex] {\n            settings.setProviderEnabled(provider: .codex, metadata: codexMeta, enabled: true)\n        }\n        if let copilotMeta = registry.metadata[.copilot] {\n            settings.setProviderEnabled(provider: .copilot, metadata: copilotMeta, enabled: true)\n        }\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n\n        let codexSnapshot = UsageSnapshot(\n            primary: RateWindow(usedPercent: 90, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            secondary: nil,\n            updatedAt: Date())\n        let copilotSnapshot = UsageSnapshot(\n            primary: RateWindow(usedPercent: 100, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            secondary: RateWindow(usedPercent: 20, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            updatedAt: Date())\n\n        store._setSnapshotForTesting(codexSnapshot, provider: .codex)\n        store._setSnapshotForTesting(copilotSnapshot, provider: .copilot)\n\n        let highest = store.providerWithHighestUsage()\n        #expect(highest?.provider == .copilot)\n        #expect(highest?.usedPercent == 100)\n    }\n\n    @Test\n    func `automatic metric excludes copilot when both lanes are exhausted`() {\n        let settings = SettingsStore(\n            configStore: testConfigStore(suiteName: \"UsageStoreHighestUsageTests-copilot-both-100\"),\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n        settings.refreshFrequency = .manual\n        settings.statusChecksEnabled = false\n        settings.setMenuBarMetricPreference(.automatic, for: .copilot)\n\n        let registry = ProviderRegistry.shared\n        if let codexMeta = registry.metadata[.codex] {\n            settings.setProviderEnabled(provider: .codex, metadata: codexMeta, enabled: true)\n        }\n        if let copilotMeta = registry.metadata[.copilot] {\n            settings.setProviderEnabled(provider: .copilot, metadata: copilotMeta, enabled: true)\n        }\n\n        let fetcher = UsageFetcher()\n        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)\n\n        let codexSnapshot = UsageSnapshot(\n            primary: RateWindow(usedPercent: 80, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            secondary: nil,\n            updatedAt: Date())\n        let copilotSnapshot = UsageSnapshot(\n            primary: RateWindow(usedPercent: 100, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            secondary: RateWindow(usedPercent: 100, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            updatedAt: Date())\n\n        store._setSnapshotForTesting(codexSnapshot, provider: .codex)\n        store._setSnapshotForTesting(copilotSnapshot, provider: .copilot)\n\n        let highest = store.providerWithHighestUsage()\n        #expect(highest?.provider == .codex)\n        #expect(highest?.usedPercent == 80)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/UsageStorePathDebugTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n@testable import CodexBar\n\n@MainActor\nstruct UsageStorePathDebugTests {\n    @Test\n    func `refresh path debug info populates snapshot`() async throws {\n        let suite = \"UsageStorePathDebugTests-path\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n        let settings = SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore())\n        let store = UsageStore(\n            fetcher: UsageFetcher(),\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            settings: settings,\n            startupBehavior: .full)\n\n        let deadline = Date().addingTimeInterval(2)\n        while store.pathDebugInfo == .empty, Date() < deadline {\n            try? await Task.sleep(nanoseconds: 50_000_000)\n        }\n\n        #expect(store.pathDebugInfo != .empty)\n        #expect(store.pathDebugInfo.effectivePATH.isEmpty == false)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/UsageStoreSessionQuotaTransitionTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n@testable import CodexBar\n\n@MainActor\nstruct UsageStoreSessionQuotaTransitionTests {\n    @MainActor\n    final class SessionQuotaNotifierSpy: SessionQuotaNotifying {\n        private(set) var posts: [(transition: SessionQuotaTransition, provider: UsageProvider)] = []\n\n        func post(transition: SessionQuotaTransition, provider: UsageProvider, badge _: NSNumber?) {\n            self.posts.append((transition: transition, provider: provider))\n        }\n    }\n\n    @Test\n    func `copilot switch from primary to secondary resets baseline`() {\n        let settings = SettingsStore(\n            configStore: testConfigStore(suiteName: \"UsageStoreSessionQuotaTransitionTests-primary-secondary\"),\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n        settings.refreshFrequency = .manual\n        settings.statusChecksEnabled = false\n        settings.sessionQuotaNotificationsEnabled = true\n\n        let notifier = SessionQuotaNotifierSpy()\n        let store = UsageStore(\n            fetcher: UsageFetcher(),\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            settings: settings,\n            sessionQuotaNotifier: notifier)\n\n        let primarySnapshot = UsageSnapshot(\n            primary: RateWindow(usedPercent: 20, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            secondary: nil,\n            updatedAt: Date())\n        store.handleSessionQuotaTransition(provider: .copilot, snapshot: primarySnapshot)\n\n        let secondarySnapshot = UsageSnapshot(\n            primary: nil,\n            secondary: RateWindow(usedPercent: 100, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            updatedAt: Date())\n        store.handleSessionQuotaTransition(provider: .copilot, snapshot: secondarySnapshot)\n\n        #expect(notifier.posts.isEmpty)\n    }\n\n    @Test\n    func `copilot switch from secondary to primary resets baseline`() {\n        let settings = SettingsStore(\n            configStore: testConfigStore(suiteName: \"UsageStoreSessionQuotaTransitionTests-secondary-primary\"),\n            zaiTokenStore: NoopZaiTokenStore(),\n            syntheticTokenStore: NoopSyntheticTokenStore())\n        settings.refreshFrequency = .manual\n        settings.statusChecksEnabled = false\n        settings.sessionQuotaNotificationsEnabled = true\n\n        let notifier = SessionQuotaNotifierSpy()\n        let store = UsageStore(\n            fetcher: UsageFetcher(),\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            settings: settings,\n            sessionQuotaNotifier: notifier)\n\n        let secondarySnapshot = UsageSnapshot(\n            primary: nil,\n            secondary: RateWindow(usedPercent: 20, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            updatedAt: Date())\n        store.handleSessionQuotaTransition(provider: .copilot, snapshot: secondarySnapshot)\n\n        let primarySnapshot = UsageSnapshot(\n            primary: RateWindow(usedPercent: 100, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            secondary: nil,\n            updatedAt: Date())\n        store.handleSessionQuotaTransition(provider: .copilot, snapshot: primarySnapshot)\n\n        #expect(notifier.posts.isEmpty)\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/WarpUsageFetcherTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\nstruct WarpUsageFetcherTests {\n    @Test\n    func `parses snapshot and aggregates bonus credits`() throws {\n        let json = \"\"\"\n        {\n          \"data\": {\n            \"user\": {\n              \"__typename\": \"UserOutput\",\n              \"user\": {\n                \"requestLimitInfo\": {\n                  \"isUnlimited\": false,\n                  \"nextRefreshTime\": \"2026-02-28T19:16:33.462988Z\",\n                  \"requestLimit\": 1500,\n                  \"requestsUsedSinceLastRefresh\": 5\n                },\n                \"bonusGrants\": [\n                  {\n                    \"requestCreditsGranted\": 20,\n                    \"requestCreditsRemaining\": 10,\n                    \"expiration\": \"2026-03-01T10:00:00Z\"\n                  }\n                ],\n                \"workspaces\": [\n                  {\n                    \"bonusGrantsInfo\": {\n                      \"grants\": [\n                        {\n                          \"requestCreditsGranted\": \"15\",\n                          \"requestCreditsRemaining\": \"5\",\n                          \"expiration\": \"2026-03-15T10:00:00Z\"\n                        }\n                      ]\n                    }\n                  }\n                ]\n              }\n            }\n          }\n        }\n        \"\"\"\n\n        let snapshot = try WarpUsageFetcher._parseResponseForTesting(Data(json.utf8))\n        let formatter = ISO8601DateFormatter()\n        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]\n        let expectedRefresh = formatter.date(from: \"2026-02-28T19:16:33.462988Z\")\n        let expectedExpiry = ISO8601DateFormatter().date(from: \"2026-03-01T10:00:00Z\")\n\n        #expect(snapshot.requestLimit == 1500)\n        #expect(snapshot.requestsUsed == 5)\n        #expect(snapshot.isUnlimited == false)\n        #expect(snapshot.nextRefreshTime != nil)\n        #expect(abs((snapshot.nextRefreshTime?.timeIntervalSince1970 ?? 0) -\n                (expectedRefresh?.timeIntervalSince1970 ?? 0))\n            < 0.5)\n        #expect(snapshot.bonusCreditsTotal == 35)\n        #expect(snapshot.bonusCreditsRemaining == 15)\n        #expect(snapshot.bonusNextExpirationRemaining == 10)\n        #expect(abs((snapshot.bonusNextExpiration?.timeIntervalSince1970 ?? 0) -\n                (expectedExpiry?.timeIntervalSince1970 ?? 0))\n            < 0.5)\n    }\n\n    @Test\n    func `graph QL errors throw API error`() {\n        let json = \"\"\"\n        {\n          \"errors\": [\n            { \"message\": \"Unauthorized\" }\n          ]\n        }\n        \"\"\"\n\n        #expect {\n            _ = try WarpUsageFetcher._parseResponseForTesting(Data(json.utf8))\n        } throws: { error in\n            guard case let WarpUsageError.apiError(code, message) = error else { return false }\n            return code == 200 && message.contains(\"Unauthorized\")\n        }\n    }\n\n    @Test\n    func `null unlimited and string numerics parse safely`() throws {\n        let json = \"\"\"\n        {\n          \"data\": {\n            \"user\": {\n              \"__typename\": \"UserOutput\",\n              \"user\": {\n                \"requestLimitInfo\": {\n                  \"isUnlimited\": null,\n                  \"nextRefreshTime\": \"2026-02-28T19:16:33Z\",\n                  \"requestLimit\": \"1500\",\n                  \"requestsUsedSinceLastRefresh\": \"5\"\n                }\n              }\n            }\n          }\n        }\n        \"\"\"\n\n        let snapshot = try WarpUsageFetcher._parseResponseForTesting(Data(json.utf8))\n\n        #expect(snapshot.isUnlimited == false)\n        #expect(snapshot.requestLimit == 1500)\n        #expect(snapshot.requestsUsed == 5)\n        #expect(snapshot.nextRefreshTime != nil)\n    }\n\n    @Test\n    func `unexpected typename returns parse error`() {\n        let json = \"\"\"\n        {\n          \"data\": {\n            \"user\": {\n              \"__typename\": \"AuthError\"\n            }\n          }\n        }\n        \"\"\"\n\n        #expect {\n            _ = try WarpUsageFetcher._parseResponseForTesting(Data(json.utf8))\n        } throws: { error in\n            guard case let WarpUsageError.parseFailed(message) = error else { return false }\n            return message.contains(\"Unexpected user type\")\n        }\n    }\n\n    @Test\n    func `missing request limit info returns parse error`() {\n        let json = \"\"\"\n        {\n          \"data\": {\n            \"user\": {\n              \"__typename\": \"UserOutput\",\n              \"user\": {}\n            }\n          }\n        }\n        \"\"\"\n\n        #expect {\n            _ = try WarpUsageFetcher._parseResponseForTesting(Data(json.utf8))\n        } throws: { error in\n            guard case let WarpUsageError.parseFailed(message) = error else { return false }\n            return message.contains(\"requestLimitInfo\")\n        }\n    }\n\n    @Test\n    func `invalid root returns parse error`() {\n        let json = \"\"\"\n        [{ \"data\": {} }]\n        \"\"\"\n\n        #expect {\n            _ = try WarpUsageFetcher._parseResponseForTesting(Data(json.utf8))\n        } throws: { error in\n            guard case let WarpUsageError.parseFailed(message) = error else { return false }\n            return message == \"Root JSON is not an object.\"\n        }\n    }\n\n    @Test\n    func `to usage snapshot omits secondary when no bonus credits`() {\n        let source = WarpUsageSnapshot(\n            requestLimit: 100,\n            requestsUsed: 10,\n            nextRefreshTime: Date().addingTimeInterval(3600),\n            isUnlimited: false,\n            updatedAt: Date(),\n            bonusCreditsRemaining: 0,\n            bonusCreditsTotal: 0,\n            bonusNextExpiration: nil,\n            bonusNextExpirationRemaining: 0)\n\n        let snapshot = source.toUsageSnapshot()\n        #expect(snapshot.secondary == nil)\n    }\n\n    @Test\n    func `to usage snapshot keeps bonus window when bonus exists`() throws {\n        let source = WarpUsageSnapshot(\n            requestLimit: 100,\n            requestsUsed: 10,\n            nextRefreshTime: Date().addingTimeInterval(3600),\n            isUnlimited: false,\n            updatedAt: Date(),\n            bonusCreditsRemaining: 0,\n            bonusCreditsTotal: 20,\n            bonusNextExpiration: nil,\n            bonusNextExpirationRemaining: 0)\n\n        let snapshot = source.toUsageSnapshot()\n        let secondary = try #require(snapshot.secondary)\n        #expect(secondary.usedPercent == 100)\n    }\n\n    @Test\n    func `to usage snapshot unlimited primary does not show reset date`() throws {\n        let source = WarpUsageSnapshot(\n            requestLimit: 0,\n            requestsUsed: 0,\n            nextRefreshTime: Date().addingTimeInterval(3600),\n            isUnlimited: true,\n            updatedAt: Date(),\n            bonusCreditsRemaining: 0,\n            bonusCreditsTotal: 0,\n            bonusNextExpiration: nil,\n            bonusNextExpirationRemaining: 0)\n\n        let snapshot = source.toUsageSnapshot()\n        let primary = try #require(snapshot.primary)\n        #expect(primary.resetsAt == nil)\n        #expect(primary.resetDescription == \"Unlimited\")\n    }\n\n    @Test\n    func `api error summary includes plain text bodies`() {\n        // Regression: Warp edge returns 429 with a non-JSON body (\"Rate exceeded.\") when User-Agent is missing/wrong.\n        let summary = WarpUsageFetcher._apiErrorSummaryForTesting(\n            statusCode: 429,\n            data: Data(\"Rate exceeded.\".utf8))\n        #expect(summary.contains(\"Rate exceeded.\"))\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/WebKitTeardownTests.swift",
    "content": "#if os(macOS)\nimport AppKit\nimport Testing\n@testable import CodexBarCore\n\n@MainActor\nstruct WebKitTeardownTests {\n    final class Owner {}\n\n    @Test\n    func `schedule cleanup registers owner`() {\n        let owner = Owner()\n        WebKitTeardown.resetForTesting()\n        WebKitTeardown.scheduleCleanup(owner: owner, window: nil, webView: nil)\n\n        #expect(WebKitTeardown.isRetainedForTesting(owner))\n        #expect(WebKitTeardown.isScheduledForTesting(owner))\n\n        WebKitTeardown.resetForTesting()\n        #expect(!WebKitTeardown.isRetainedForTesting(owner))\n        #expect(!WebKitTeardown.isScheduledForTesting(owner))\n    }\n}\n#endif\n"
  },
  {
    "path": "Tests/CodexBarTests/WidgetSnapshotTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\nstruct WidgetSnapshotTests {\n    @Test\n    func `widget snapshot round trip`() throws {\n        let entry = WidgetSnapshot.ProviderEntry(\n            provider: .codex,\n            updatedAt: Date(),\n            primary: RateWindow(usedPercent: 10, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            secondary: RateWindow(usedPercent: 20, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            tertiary: nil,\n            creditsRemaining: 123.4,\n            codeReviewRemainingPercent: 80,\n            tokenUsage: WidgetSnapshot.TokenUsageSummary(\n                sessionCostUSD: 12.3,\n                sessionTokens: 1200,\n                last30DaysCostUSD: 456.7,\n                last30DaysTokens: 9800),\n            dailyUsage: [\n                WidgetSnapshot.DailyUsagePoint(dayKey: \"2025-12-20\", totalTokens: 1200, costUSD: 12.3),\n            ])\n\n        let snapshot = WidgetSnapshot(\n            entries: [entry],\n            enabledProviders: [.codex, .claude],\n            generatedAt: Date())\n\n        let encoder = JSONEncoder()\n        encoder.dateEncodingStrategy = .iso8601\n        let data = try encoder.encode(snapshot)\n\n        let decoder = JSONDecoder()\n        decoder.dateDecodingStrategy = .iso8601\n        let decoded = try decoder.decode(WidgetSnapshot.self, from: data)\n\n        #expect(decoded.entries.count == 1)\n        #expect(decoded.entries.first?.provider == .codex)\n        #expect(decoded.entries.first?.tokenUsage?.sessionTokens == 1200)\n        #expect(decoded.enabledProviders == [.codex, .claude])\n    }\n\n    @Test\n    func `widget snapshot round trip preserves kilo provider`() throws {\n        let entry = WidgetSnapshot.ProviderEntry(\n            provider: .kilo,\n            updatedAt: Date(),\n            primary: RateWindow(usedPercent: 40, windowMinutes: nil, resetsAt: nil, resetDescription: \"40/100 credits\"),\n            secondary: nil,\n            tertiary: nil,\n            creditsRemaining: nil,\n            codeReviewRemainingPercent: nil,\n            tokenUsage: WidgetSnapshot.TokenUsageSummary(\n                sessionCostUSD: 1.25,\n                sessionTokens: 4200,\n                last30DaysCostUSD: 19.75,\n                last30DaysTokens: 58000),\n            dailyUsage: [\n                WidgetSnapshot.DailyUsagePoint(dayKey: \"2026-02-27\", totalTokens: 4200, costUSD: 1.25),\n            ])\n\n        let snapshot = WidgetSnapshot(\n            entries: [entry],\n            enabledProviders: [.kilo, .codex],\n            generatedAt: Date())\n\n        let encoder = JSONEncoder()\n        encoder.dateEncodingStrategy = .iso8601\n        let data = try encoder.encode(snapshot)\n\n        let decoder = JSONDecoder()\n        decoder.dateDecodingStrategy = .iso8601\n        let decoded = try decoder.decode(WidgetSnapshot.self, from: data)\n\n        #expect(decoded.entries.first?.provider == .kilo)\n        #expect(decoded.entries.first?.primary?.resetDescription == \"40/100 credits\")\n        #expect(decoded.enabledProviders == [.kilo, .codex])\n    }\n\n    @Test\n    func `widget snapshot round trip preserves kilo zero total edge state`() throws {\n        let now = Date()\n        let kiloSnapshot = KiloUsageSnapshot(\n            creditsUsed: 0,\n            creditsTotal: 0,\n            creditsRemaining: 0,\n            planName: \"Kilo Pass Pro\",\n            autoTopUpEnabled: true,\n            autoTopUpMethod: \"visa\",\n            updatedAt: now).toUsageSnapshot()\n\n        let entry = WidgetSnapshot.ProviderEntry(\n            provider: .kilo,\n            updatedAt: now,\n            primary: kiloSnapshot.primary,\n            secondary: kiloSnapshot.secondary,\n            tertiary: kiloSnapshot.tertiary,\n            creditsRemaining: nil,\n            codeReviewRemainingPercent: nil,\n            tokenUsage: nil,\n            dailyUsage: [])\n\n        let snapshot = WidgetSnapshot(\n            entries: [entry],\n            enabledProviders: [.kilo],\n            generatedAt: now)\n\n        let encoder = JSONEncoder()\n        encoder.dateEncodingStrategy = .iso8601\n        let data = try encoder.encode(snapshot)\n\n        let decoder = JSONDecoder()\n        decoder.dateDecodingStrategy = .iso8601\n        let decoded = try decoder.decode(WidgetSnapshot.self, from: data)\n\n        #expect(decoded.entries.first?.provider == .kilo)\n        #expect(decoded.entries.first?.primary?.usedPercent == 100)\n        #expect(decoded.entries.first?.primary?.remainingPercent == 0)\n        #expect(decoded.entries.first?.primary?.resetDescription == \"0/0 credits\")\n        #expect(decoded.enabledProviders == [.kilo])\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/ZaiAvailabilityTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n@testable import CodexBar\n\n@MainActor\nstruct ZaiAvailabilityTests {\n    @Test\n    func `enables zai when token exists in store`() throws {\n        let suite = \"ZaiAvailabilityTests-token\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n\n        let tokenStore = StubZaiTokenStore(token: \"zai-test-token\")\n        let settings = SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: tokenStore)\n        let store = UsageStore(\n            fetcher: UsageFetcher(environment: [:]),\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            settings: settings)\n\n        let metadata = try #require(ProviderRegistry.shared.metadata[.zai])\n        settings.setProviderEnabled(provider: .zai, metadata: metadata, enabled: true)\n\n        #expect(store.isEnabled(.zai) == true)\n        #expect(settings.zaiAPIToken == \"zai-test-token\")\n    }\n\n    @Test\n    func `enables zai when token exists in token accounts`() throws {\n        let suite = \"ZaiAvailabilityTests-token-accounts\"\n        let defaults = try #require(UserDefaults(suiteName: suite))\n        defaults.removePersistentDomain(forName: suite)\n        let configStore = testConfigStore(suiteName: suite)\n\n        let settings = SettingsStore(\n            userDefaults: defaults,\n            configStore: configStore,\n            zaiTokenStore: NoopZaiTokenStore())\n        let store = UsageStore(\n            fetcher: UsageFetcher(environment: [:]),\n            browserDetection: BrowserDetection(cacheTTL: 0),\n            settings: settings)\n\n        settings.addTokenAccount(provider: .zai, label: \"primary\", token: \"zai-token-account\")\n\n        let metadata = try #require(ProviderRegistry.shared.metadata[.zai])\n        settings.setProviderEnabled(provider: .zai, metadata: metadata, enabled: true)\n\n        #expect(store.isEnabled(.zai) == true)\n    }\n}\n\nprivate struct StubZaiTokenStore: ZaiTokenStoring {\n    let token: String?\n\n    func loadToken() throws -> String? {\n        self.token\n    }\n\n    func storeToken(_: String?) throws {}\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/ZaiProviderTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import CodexBarCore\n\nstruct ZaiSettingsReaderTests {\n    @Test\n    func `api token reads from environment`() {\n        let token = ZaiSettingsReader.apiToken(environment: [\"Z_AI_API_KEY\": \"abc123\"])\n        #expect(token == \"abc123\")\n    }\n\n    @Test\n    func `api token strips quotes`() {\n        let token = ZaiSettingsReader.apiToken(environment: [\"Z_AI_API_KEY\": \"\\\"token-xyz\\\"\"])\n        #expect(token == \"token-xyz\")\n    }\n\n    @Test\n    func `api host reads from environment`() {\n        let host = ZaiSettingsReader.apiHost(environment: [ZaiSettingsReader.apiHostKey: \" open.bigmodel.cn \"])\n        #expect(host == \"open.bigmodel.cn\")\n    }\n\n    @Test\n    func `quota URL infers scheme`() {\n        let url = ZaiSettingsReader\n            .quotaURL(environment: [ZaiSettingsReader.quotaURLKey: \"open.bigmodel.cn/api/coding\"])\n        #expect(url?.absoluteString == \"https://open.bigmodel.cn/api/coding\")\n    }\n}\n\nstruct ZaiUsageSnapshotTests {\n    @Test\n    func `maps usage snapshot windows`() {\n        let reset = Date(timeIntervalSince1970: 123)\n        let tokenLimit = ZaiLimitEntry(\n            type: .tokensLimit,\n            unit: .hours,\n            number: 5,\n            usage: 100,\n            currentValue: 20,\n            remaining: 80,\n            percentage: 25,\n            usageDetails: [],\n            nextResetTime: reset)\n        let timeLimit = ZaiLimitEntry(\n            type: .timeLimit,\n            unit: .days,\n            number: 30,\n            usage: 200,\n            currentValue: 40,\n            remaining: 160,\n            percentage: 50,\n            usageDetails: [],\n            nextResetTime: nil)\n        let snapshot = ZaiUsageSnapshot(\n            tokenLimit: tokenLimit,\n            timeLimit: timeLimit,\n            planName: nil,\n            updatedAt: reset)\n\n        let usage = snapshot.toUsageSnapshot()\n\n        #expect(usage.primary?.usedPercent == 20)\n        #expect(usage.primary?.windowMinutes == 300)\n        #expect(usage.primary?.resetsAt == reset)\n        #expect(usage.primary?.resetDescription == \"5 hours window\")\n        #expect(usage.secondary?.usedPercent == 20)\n        #expect(usage.secondary?.resetDescription == \"30 days window\")\n        #expect(usage.zaiUsage?.tokenLimit?.usage == 100)\n    }\n\n    @Test\n    func `maps usage snapshot windows with missing fields`() {\n        let reset = Date(timeIntervalSince1970: 123)\n        let tokenLimit = ZaiLimitEntry(\n            type: .tokensLimit,\n            unit: .hours,\n            number: 5,\n            usage: nil,\n            currentValue: nil,\n            remaining: nil,\n            percentage: 25,\n            usageDetails: [],\n            nextResetTime: reset)\n        let snapshot = ZaiUsageSnapshot(\n            tokenLimit: tokenLimit,\n            timeLimit: nil,\n            planName: nil,\n            updatedAt: reset)\n\n        let usage = snapshot.toUsageSnapshot()\n\n        #expect(usage.primary?.usedPercent == 25)\n        #expect(usage.primary?.windowMinutes == 300)\n        #expect(usage.primary?.resetsAt == reset)\n        #expect(usage.primary?.resetDescription == \"5 hours window\")\n        #expect(usage.zaiUsage?.tokenLimit?.usage == nil)\n    }\n\n    @Test\n    func `maps usage snapshot windows with missing remaining uses current value`() {\n        let reset = Date(timeIntervalSince1970: 123)\n        let tokenLimit = ZaiLimitEntry(\n            type: .tokensLimit,\n            unit: .hours,\n            number: 5,\n            usage: 100,\n            currentValue: 20,\n            remaining: nil,\n            percentage: 25,\n            usageDetails: [],\n            nextResetTime: reset)\n        let snapshot = ZaiUsageSnapshot(\n            tokenLimit: tokenLimit,\n            timeLimit: nil,\n            planName: nil,\n            updatedAt: reset)\n\n        let usage = snapshot.toUsageSnapshot()\n\n        #expect(usage.primary?.usedPercent == 20)\n    }\n\n    @Test\n    func `maps usage snapshot windows with missing current value uses remaining`() {\n        let reset = Date(timeIntervalSince1970: 123)\n        let tokenLimit = ZaiLimitEntry(\n            type: .tokensLimit,\n            unit: .hours,\n            number: 5,\n            usage: 100,\n            currentValue: nil,\n            remaining: 80,\n            percentage: 25,\n            usageDetails: [],\n            nextResetTime: reset)\n        let snapshot = ZaiUsageSnapshot(\n            tokenLimit: tokenLimit,\n            timeLimit: nil,\n            planName: nil,\n            updatedAt: reset)\n\n        let usage = snapshot.toUsageSnapshot()\n\n        #expect(usage.primary?.usedPercent == 20)\n    }\n\n    @Test\n    func `maps usage snapshot windows with missing remaining and current value falls back to percentage`() {\n        let reset = Date(timeIntervalSince1970: 123)\n        let tokenLimit = ZaiLimitEntry(\n            type: .tokensLimit,\n            unit: .hours,\n            number: 5,\n            usage: 100,\n            currentValue: nil,\n            remaining: nil,\n            percentage: 25,\n            usageDetails: [],\n            nextResetTime: reset)\n        let snapshot = ZaiUsageSnapshot(\n            tokenLimit: tokenLimit,\n            timeLimit: nil,\n            planName: nil,\n            updatedAt: reset)\n\n        let usage = snapshot.toUsageSnapshot()\n\n        #expect(usage.primary?.usedPercent == 25)\n    }\n}\n\nstruct ZaiUsageParsingTests {\n    @Test\n    func `empty body returns parse failed`() {\n        #expect {\n            _ = try ZaiUsageFetcher.parseUsageSnapshot(from: Data())\n        } throws: { error in\n            guard case let ZaiUsageError.parseFailed(message) = error else { return false }\n            return message == \"Empty response body\"\n        }\n    }\n\n    @Test\n    func `parses usage response`() throws {\n        let json = \"\"\"\n        {\n          \"code\": 200,\n          \"msg\": \"Operation successful\",\n          \"data\": {\n            \"limits\": [\n              {\n                \"type\": \"TIME_LIMIT\",\n                \"unit\": 5,\n                \"number\": 1,\n                \"usage\": 100,\n                \"currentValue\": 102,\n                \"remaining\": 0,\n                \"percentage\": 100,\n                \"usageDetails\": [\n                  { \"modelCode\": \"search-prime\", \"usage\": 95 }\n                ]\n              },\n              {\n                \"type\": \"TOKENS_LIMIT\",\n                \"unit\": 3,\n                \"number\": 5,\n                \"usage\": 40000000,\n                \"currentValue\": 13628365,\n                \"remaining\": 26371635,\n                \"percentage\": 34,\n                \"nextResetTime\": 1768507567547\n              }\n            ],\n            \"planName\": \"Pro\"\n          },\n          \"success\": true\n        }\n        \"\"\"\n\n        let snapshot = try ZaiUsageFetcher.parseUsageSnapshot(from: Data(json.utf8))\n\n        #expect(snapshot.planName == \"Pro\")\n        #expect(snapshot.tokenLimit?.usage == 40_000_000)\n        #expect(snapshot.timeLimit?.usageDetails.first?.modelCode == \"search-prime\")\n        #expect(snapshot.tokenLimit?.percentage == 34.0)\n    }\n\n    @Test\n    func `missing data returns api error`() {\n        let json = \"\"\"\n        { \"code\": 1001, \"msg\": \"Authorization Token Missing\", \"success\": false }\n        \"\"\"\n\n        #expect {\n            _ = try ZaiUsageFetcher.parseUsageSnapshot(from: Data(json.utf8))\n        } throws: { error in\n            guard case let ZaiUsageError.apiError(message) = error else { return false }\n            return message == \"Authorization Token Missing\"\n        }\n    }\n\n    @Test\n    func `success without data returns parse failed`() {\n        let json = \"\"\"\n        { \"code\": 200, \"msg\": \"Operation successful\", \"success\": true }\n        \"\"\"\n\n        #expect {\n            _ = try ZaiUsageFetcher.parseUsageSnapshot(from: Data(json.utf8))\n        } throws: { error in\n            guard case let ZaiUsageError.parseFailed(message) = error else { return false }\n            return message == \"Missing data\"\n        }\n    }\n\n    @Test\n    func `success without limits parses empty usage`() throws {\n        let json = \"\"\"\n        {\n          \"code\": 200,\n          \"msg\": \"Operation successful\",\n          \"data\": { \"planName\": \"Pro\" },\n          \"success\": true\n        }\n        \"\"\"\n\n        let snapshot = try ZaiUsageFetcher.parseUsageSnapshot(from: Data(json.utf8))\n\n        #expect(snapshot.planName == \"Pro\")\n        #expect(snapshot.tokenLimit == nil)\n        #expect(snapshot.timeLimit == nil)\n    }\n\n    @Test\n    func `parses new schema with missing token limit fields`() throws {\n        let json = \"\"\"\n        {\n          \"code\": 200,\n          \"msg\": \"Operation successful\",\n          \"data\": {\n            \"limits\": [\n              {\n                \"type\": \"TIME_LIMIT\",\n                \"unit\": 5,\n                \"number\": 1,\n                \"usage\": 100,\n                \"currentValue\": 0,\n                \"remaining\": 100,\n                \"percentage\": 0,\n                \"usageDetails\": [\n                  { \"modelCode\": \"search-prime\", \"usage\": 0 },\n                  { \"modelCode\": \"web-reader\", \"usage\": 1 },\n                  { \"modelCode\": \"zread\", \"usage\": 0 }\n                ]\n              },\n              {\n                \"type\": \"TOKENS_LIMIT\",\n                \"unit\": 3,\n                \"number\": 5,\n                \"percentage\": 1,\n                \"nextResetTime\": 1770724088678\n              }\n            ]\n          },\n          \"success\": true\n        }\n        \"\"\"\n\n        let snapshot = try ZaiUsageFetcher.parseUsageSnapshot(from: Data(json.utf8))\n\n        #expect(snapshot.tokenLimit?.percentage == 1.0)\n        #expect(snapshot.tokenLimit?.usage == nil)\n        #expect(snapshot.tokenLimit?.currentValue == nil)\n        #expect(snapshot.tokenLimit?.remaining == nil)\n        #expect(snapshot.tokenLimit?.usedPercent == 1.0)\n        #expect(snapshot.tokenLimit?.windowMinutes == 300)\n        #expect(snapshot.timeLimit?.usage == 100)\n    }\n}\n\nstruct ZaiAPIRegionTests {\n    @Test\n    func `defaults to global endpoint`() {\n        let url = ZaiUsageFetcher.resolveQuotaURL(region: .global, environment: [:])\n        #expect(url.absoluteString == \"https://api.z.ai/api/monitor/usage/quota/limit\")\n    }\n\n    @Test\n    func `uses big model region when selected`() {\n        let url = ZaiUsageFetcher.resolveQuotaURL(region: .bigmodelCN, environment: [:])\n        #expect(url.absoluteString == \"https://open.bigmodel.cn/api/monitor/usage/quota/limit\")\n    }\n\n    @Test\n    func `quota url environment override wins`() {\n        let env = [ZaiSettingsReader.quotaURLKey: \"https://open.bigmodel.cn/api/coding/paas/v4\"]\n        let url = ZaiUsageFetcher.resolveQuotaURL(region: .global, environment: env)\n        #expect(url.absoluteString == \"https://open.bigmodel.cn/api/coding/paas/v4\")\n    }\n\n    @Test\n    func `api host environment appends quota path`() {\n        let env = [ZaiSettingsReader.apiHostKey: \"open.bigmodel.cn\"]\n        let url = ZaiUsageFetcher.resolveQuotaURL(region: .global, environment: env)\n        #expect(url.absoluteString == \"https://open.bigmodel.cn/api/monitor/usage/quota/limit\")\n    }\n}\n"
  },
  {
    "path": "Tests/CodexBarTests/ZaiTokenStoreTestSupport.swift",
    "content": "@testable import CodexBar\n\nstruct NoopZaiTokenStore: ZaiTokenStoring {\n    func loadToken() throws -> String? {\n        nil\n    }\n\n    func storeToken(_: String?) throws {}\n}\n\nstruct NoopSyntheticTokenStore: SyntheticTokenStoring {\n    func loadToken() throws -> String? {\n        nil\n    }\n\n    func storeToken(_: String?) throws {}\n}\n"
  },
  {
    "path": "TestsLinux/JetBrainsParserLinuxTests.swift",
    "content": "import CodexBarCore\nimport Foundation\nimport Testing\n\n/// Tests for the regex-based JetBrains XML parser used on Linux.\n/// These tests verify that the non-libxml2 implementation correctly parses\n/// JetBrains AI Assistant quota files.\n@Suite\nstruct JetBrainsParserLinuxTests {\n    @Test\n    func parsesQuotaXMLWithBothOptions() throws {\n        let quotaInfo = [\n            \"{&#10;  &quot;type&quot;: &quot;Available&quot;,\",\n            \"&#10;  &quot;current&quot;: &quot;7478.3&quot;,\",\n            \"&#10;  &quot;maximum&quot;: &quot;1000000&quot;,\",\n            \"&#10;  &quot;until&quot;: &quot;2026-11-09T21:00:00Z&quot;,\",\n            \"&#10;  &quot;tariffQuota&quot;: {\",\n            \"&#10;    &quot;current&quot;: &quot;7478.3&quot;,\",\n            \"&#10;    &quot;maximum&quot;: &quot;1000000&quot;,\",\n            \"&#10;    &quot;available&quot;: &quot;992521.7&quot;\",\n            \"&#10;  }&#10;}\",\n        ].joined()\n        let nextRefill = [\n            \"{&#10;  &quot;type&quot;: &quot;Known&quot;,\",\n            \"&#10;  &quot;next&quot;: &quot;2026-01-16T14:00:54.939Z&quot;,\",\n            \"&#10;  &quot;tariff&quot;: {\",\n            \"&#10;    &quot;amount&quot;: &quot;1000000&quot;,\",\n            \"&#10;    &quot;duration&quot;: &quot;PT720H&quot;\",\n            \"&#10;  }&#10;}\",\n        ].joined()\n\n        let xml = \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <application>\n          <component name=\"AIAssistantQuotaManager2\">\n            <option name=\"quotaInfo\" value=\"\\(quotaInfo)\" />\n            <option name=\"nextRefill\" value=\"\\(nextRefill)\" />\n          </component>\n        </application>\n        \"\"\"\n\n        let data = Data(xml.utf8)\n        let snapshot = try JetBrainsStatusProbe.parseXMLData(data, detectedIDE: nil)\n\n        #expect(snapshot.quotaInfo.type == \"Available\")\n        #expect(snapshot.quotaInfo.used == 7478.3)\n        #expect(snapshot.quotaInfo.maximum == 1_000_000)\n        #expect(snapshot.quotaInfo.available == 992_521.7)\n        #expect(snapshot.refillInfo?.type == \"Known\")\n        #expect(snapshot.refillInfo?.amount == 1_000_000)\n    }\n\n    @Test\n    func parsesQuotaXMLWithOnlyQuotaInfo() throws {\n        let quotaInfo = \"{&quot;type&quot;:&quot;free&quot;,&quot;current&quot;:&quot;5000&quot;,&quot;maximum&quot;:&quot;100000&quot;}\"\n\n        let xml = \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <application>\n          <component name=\"AIAssistantQuotaManager2\">\n            <option name=\"quotaInfo\" value=\"\\(quotaInfo)\" />\n          </component>\n        </application>\n        \"\"\"\n\n        let data = Data(xml.utf8)\n        let snapshot = try JetBrainsStatusProbe.parseXMLData(data, detectedIDE: nil)\n\n        #expect(snapshot.quotaInfo.type == \"free\")\n        #expect(snapshot.quotaInfo.used == 5000)\n        #expect(snapshot.quotaInfo.maximum == 100_000)\n        #expect(snapshot.refillInfo == nil)\n    }\n\n    @Test\n    func parsesXMLWithReversedAttributeOrder() throws {\n        // Test that parser handles value before name (different attribute order)\n        let quotaInfo = \"{&quot;type&quot;:&quot;paid&quot;,&quot;current&quot;:&quot;1000&quot;,&quot;maximum&quot;:&quot;50000&quot;}\"\n\n        let xml = \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <application>\n          <component name=\"AIAssistantQuotaManager2\">\n            <option value=\"\\(quotaInfo)\" name=\"quotaInfo\" />\n          </component>\n        </application>\n        \"\"\"\n\n        let data = Data(xml.utf8)\n        let snapshot = try JetBrainsStatusProbe.parseXMLData(data, detectedIDE: nil)\n\n        #expect(snapshot.quotaInfo.type == \"paid\")\n        #expect(snapshot.quotaInfo.used == 1000)\n        #expect(snapshot.quotaInfo.maximum == 50000)\n    }\n\n    @Test\n    func handlesHTMLEntities() throws {\n        let quotaInfo = [\n            \"{&quot;type&quot;:&quot;test&quot;\",\n            \",&quot;current&quot;:&quot;0&quot;\",\n            \",&quot;maximum&quot;:&quot;50000&quot;}\",\n        ].joined()\n\n        let xml = \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <application>\n          <component name=\"AIAssistantQuotaManager2\">\n            <option name=\"quotaInfo\" value=\"\\(quotaInfo)\" />\n          </component>\n        </application>\n        \"\"\"\n\n        let data = Data(xml.utf8)\n        let snapshot = try JetBrainsStatusProbe.parseXMLData(data, detectedIDE: nil)\n\n        #expect(snapshot.quotaInfo.type == \"test\")\n        #expect(snapshot.quotaInfo.maximum == 50000)\n    }\n\n    @Test\n    func throwsOnMissingQuotaInfo() throws {\n        let xml = \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <application>\n          <component name=\"AIAssistantQuotaManager2\">\n          </component>\n        </application>\n        \"\"\"\n\n        let data = Data(xml.utf8)\n        #expect(throws: JetBrainsStatusProbeError.noQuotaInfo) {\n            _ = try JetBrainsStatusProbe.parseXMLData(data, detectedIDE: nil)\n        }\n    }\n\n    @Test\n    func throwsOnMissingComponent() throws {\n        let xml = \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <application>\n          <component name=\"SomeOtherComponent\">\n            <option name=\"quotaInfo\" value=\"{}\" />\n          </component>\n        </application>\n        \"\"\"\n\n        let data = Data(xml.utf8)\n        #expect(throws: JetBrainsStatusProbeError.noQuotaInfo) {\n            _ = try JetBrainsStatusProbe.parseXMLData(data, detectedIDE: nil)\n        }\n    }\n\n    @Test\n    func throwsOnEmptyQuotaInfo() throws {\n        let xml = \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <application>\n          <component name=\"AIAssistantQuotaManager2\">\n            <option name=\"quotaInfo\" value=\"\" />\n          </component>\n        </application>\n        \"\"\"\n\n        let data = Data(xml.utf8)\n        #expect(throws: JetBrainsStatusProbeError.noQuotaInfo) {\n            _ = try JetBrainsStatusProbe.parseXMLData(data, detectedIDE: nil)\n        }\n    }\n\n    @Test\n    func parsesXMLWithSingleQuotes() throws {\n        let quotaInfo = \"{&quot;type&quot;:&quot;single&quot;,&quot;current&quot;:&quot;100&quot;,&quot;maximum&quot;:&quot;10000&quot;}\"\n\n        let xml = \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <application>\n          <component name='AIAssistantQuotaManager2'>\n            <option name='quotaInfo' value='\\(quotaInfo)' />\n          </component>\n        </application>\n        \"\"\"\n\n        let data = Data(xml.utf8)\n        let snapshot = try JetBrainsStatusProbe.parseXMLData(data, detectedIDE: nil)\n\n        #expect(snapshot.quotaInfo.type == \"single\")\n        #expect(snapshot.quotaInfo.maximum == 10000)\n    }\n}\n"
  },
  {
    "path": "TestsLinux/PlatformGatingTests.swift",
    "content": "import CodexBarCore\nimport Testing\n\n@Suite\nstruct PlatformGatingTests {\n    @Test\n    func claudeWebFetcher_isNotSupportedOnLinux() async {\n        #if os(Linux)\n        let error = await #expect(throws: ClaudeWebAPIFetcher.FetchError.self) {\n            _ = try await ClaudeWebAPIFetcher.fetchUsage()\n        }\n        let isExpectedError = error.map { thrown in\n            if case .notSupportedOnThisPlatform = thrown { return true }\n            return false\n        } ?? false\n        #expect(isExpectedError)\n        #else\n        #expect(Bool(true))\n        #endif\n    }\n\n    @Test\n    func claudeWebFetcher_hasSessionKey_isFalseOnLinux() {\n        #if os(Linux)\n        #expect(ClaudeWebAPIFetcher.hasSessionKey(cookieHeader: nil) == false)\n        #else\n        #expect(Bool(true))\n        #endif\n    }\n\n    @Test\n    func claudeWebFetcher_sessionKeyInfo_throwsOnLinux() {\n        #if os(Linux)\n        let error = #expect(throws: ClaudeWebAPIFetcher.FetchError.self) {\n            _ = try ClaudeWebAPIFetcher.sessionKeyInfo()\n        }\n        let isExpectedError = error.map { thrown in\n            if case .notSupportedOnThisPlatform = thrown { return true }\n            return false\n        } ?? false\n        #expect(isExpectedError)\n        #else\n        #expect(Bool(true))\n        #endif\n    }\n}\n"
  },
  {
    "path": "appcast.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>\n<rss xmlns:sparkle=\"http://www.andymatuschak.org/xml-namespaces/sparkle\" version=\"2.0\">\n    <channel>\n        <title>CodexBar</title>\n        <item>\n            <title>0.18.0</title>\n            <pubDate>Sun, 15 Mar 2026 22:16:41 -0700</pubDate>\n            <link>https://raw.githubusercontent.com/steipete/CodexBar/main/appcast.xml</link>\n            <sparkle:version>52</sparkle:version>\n            <sparkle:shortVersionString>0.18.0</sparkle:shortVersionString>\n            <sparkle:minimumSystemVersion>14.0</sparkle:minimumSystemVersion>\n            <description><![CDATA[<h2>CodexBar 0.18.0</h2>\n<h3>Highlights</h3>\n<ul>\n<li>Add Kilo provider support with API/CLI source modes, widget integration, and pass/credit handling (#454). Built on work by @coreh.</li>\n<li>Add Ollama provider, including token-account support in Settings and CLI (#380). Thanks @CryptoSageSnr!</li>\n<li>Add OpenRouter provider for credit-based usage tracking (#396). Thanks @chountalas!</li>\n<li>Add Codex historical pace with risk forecasting, backfill, and zero-usage-day handling (#482, supersedes #438). Thanks @tristanmanchester!</li>\n<li>Add a merged-menu Overview tab with configurable providers and row-to-provider navigation (#416). @ratulsarna</li>\n<li>Add an experimental option to suppress Claude Keychain prompts (#388).</li>\n<li>Reduce CPU/energy regressions and JSONL scanner overhead in Codex/web usage paths (#402, #392). Thanks @bald-ai and @asonawalla!</li>\n</ul>\n<h3>Providers & Usage</h3>\n<ul>\n<li>Codex: add historical pace risk forecasting and backfill, gate pace computation by display mode, and handle zero-usage days in historical data (#482, supersedes #438). Thanks @tristanmanchester!</li>\n<li>Kilo: add provider support with source-mode fallback, clearer credential/login guidance, auto top-up activity labeling, zero-balance credit handling, and pass parsing/menu rendering (#454). Thanks @coreh!</li>\n<li>Ollama: add provider support with token-account support in app/CLI, Chrome-default auto cookie import, and manual-cookie mode (#380). Thanks @CryptoSageSnr!</li>\n<li>OpenRouter: add provider support with credit tracking, key-quota popup support, token-account labels, fallback status icons, and updated icon/color (#396). Thanks @chountalas!</li>\n<li>Gemini: show separate Pro, Flash, and Flash Lite meters by splitting Gemini CLI quota buckets for <code>gemini-2.5-flash</code> and <code>gemini-2.5-flash-lite</code> (#496). Thanks @aladh</li>\n<li>Codex: in percent display mode with \"show remaining,\" show remaining credits in the menu bar when session or weekly usage is exhausted (#336). Thanks @teron131!</li>\n<li>Claude: surface rate-limit errors from the CLI <code>/usage</code> probe with a user-friendly message, and harden \"Failed to load usage data\" matching against whitespace-collapsed output.</li>\n<li>Claude: restore weekly/Sonnet reset parsing from whitespace-collapsed CLI <code>/usage</code> output so reset times and pace details still appear after CLI fallback.</li>\n<li>Claude: fix extra-usage double conversion so OAuth/Web values stay on a single normalization path (#472, supersedes #463). Thanks @Priyans-hu!</li>\n<li>Claude: remove root-directory mtime short-circuiting in cost scanning so new session logs inside existing <code>~/.claude/projects/*</code> folders are discovered reliably (#462, fixes #411). Thanks @Priyans-hu!</li>\n<li>Copilot: harden free-plan quota parsing and fallback behavior by treating underdetermined values as unknown, preserving missing metadata as nil (#432, supersedes #393). Thanks @emanuelst!</li>\n<li>OpenCode: treat explicit <code>null</code> subscription responses as missing usage data, skip POST fallback, and return a clearer workspace-specific error (#412).</li>\n<li>OpenCode: surface clearer HTTP errors. Thanks @SalimBinYousuf1!</li>\n<li>Codex: preserve exact GPT-5 model IDs in local cost history, add GPT-5.4 pricing, and label zero-cost <code>gpt-5.3-codex-spark</code> sessions as \"Research Preview\" in cost breakdowns (#511). Thanks @iam-brain!</li>\n<li>Augment: prevent refresh stalls when <code>auggie account status</code> hangs by replacing unbounded CLI waits with timed subprocess execution and fallback handling (#481). Thanks @bryant24hao!</li>\n<li>Update Kiro parsing for <code>kiro-cli</code> 1.24+ / Q Developer formats and non-managed plan handling (#288). Thanks @kilhyeonjun!</li>\n<li>Kimi: in automatic metric mode, prioritize the 5-hour rate-limit window for menu bar and merged highest-usage calculations (#390). Thanks @ajaxjiang96!</li>\n<li>Browser cookie import: match Gecko <code>*.default*</code> profile directories case-insensitively so Firefox/Zen cookie detection works with uppercase <code>.Default</code> directories (#422). Thanks @bald-ai!</li>\n<li>MiniMax: make both Settings \"Open Coding Plan\" actions region-aware so China mainland selection opens <code>platform.minimaxi.com</code> instead of the global domain (#426, fixes #378). Thanks @bald-ai!</li>\n<li>Menu: rebuild the merged provider switcher when “Show usage as used” changes so switcher progress updates immediately (#306). Thanks @Flohhhhh!</li>\n<li>Warp: update API key setup guidance.</li>\n<li>Claude: update the \"not installed\" help link to the current Claude Code documentation URL (#431). Thanks @skebby11!</li>\n<li>Fix Claude setup message package name (#376). Thanks @daegwang!</li>\n</ul>\n<h3>Menu & Settings</h3>\n<ul>\n<li>Merged menu: keep Merge Icons, the switcher, and Overview tied to user-enabled providers even when some providers are temporarily unavailable, while defaulting menu content and icon state to an available provider when possible (#525). Thanks @Astro-Han!</li>\n<li>Merged menu: add an Overview switcher tab that shows up to three provider usage rows in provider order (#416).</li>\n<li>Settings: add \"Overview tab providers\" controls to choose/deselect Overview providers, with persisted selection reconciliation as enabled providers change (#416).</li>\n<li>Menu: hide contextual provider actions while Overview is selected and rebuild switcher state when overview availability changes (#416).</li>\n</ul>\n<h3>Claude OAuth & Keychain</h3>\n<ul>\n<li>Add an experimental Claude OAuth Security-CLI reader path and option in settings.</li>\n<li>Apply stored prompt mode and fallback policy to silent/noninteractive keychain probes.</li>\n<li>Add cooldown for background OAuth keychain retries.</li>\n<li>Disable experimental toggle when keychain access is disabled.</li>\n<li>Use a <code>claude-code/<version></code> User-Agent for OAuth usage requests instead of a generic identifier.</li>\n</ul>\n<h3>Performance & Reliability</h3>\n<ul>\n<li>Codex/OpenAI web: reduce CPU and energy overhead by shortening failed CLI probe windows, capping web retry timeouts, and using adaptive idle blink scheduling (#402). Thanks @bald-ai!</li>\n<li>Cost usage scanner: optimize JSONL chunk parsing to avoid buffer-front removal overhead on large logs (#392). Thanks @asonawalla!</li>\n<li>TTY runner: fence shutdown registration to avoid launch/shutdown races, isolate process groups before shutdown rejection, and ensure lingering CLI descendants are cleaned up on app termination (#429). Thanks @uraimo!</li>\n</ul>\n<p><a href=\"https://github.com/steipete/CodexBar/blob/main/CHANGELOG.md\">View full changelog</a></p>\n]]></description>\n            <enclosure url=\"https://github.com/steipete/CodexBar/releases/download/v0.18.0/CodexBar-0.18.0.zip\" length=\"24883192\" type=\"application/octet-stream\" sparkle:edSignature=\"Nyk09cgmc5u8lJcdkuP5iY79jeOrRbXQ9vYIJEEsmCzCKvs++GKEiEp5+w4MsKkKk99S2gBiVkpVlZU6UkIsCw==\"/>\n        </item>\n        <item sparkle:channel=\"beta\">\n            <title>0.18.0-beta.3</title>\n            <pubDate>Fri, 13 Feb 2026 18:57:54 +0100</pubDate>\n            <link>https://raw.githubusercontent.com/steipete/CodexBar/main/appcast.xml</link>\n            <sparkle:version>51</sparkle:version>\n            <sparkle:shortVersionString>0.18.0-beta.3</sparkle:shortVersionString>\n            <sparkle:minimumSystemVersion>14.0</sparkle:minimumSystemVersion>\n            <description><![CDATA[<h2>CodexBar 0.18.0-beta.3</h2>\n<h3>Highlights</h3>\n<ul>\n<li>Claude OAuth/keychain flows were reworked across a series of follow-up PRs to reduce prompt storms, stabilize background behavior, surface a setting to control prompt policy and make failure modes deterministic (#245, #305, #308, #309, #364). Thanks @manikv12!</li>\n<li>Claude: harden Claude Code PTY capture for <code>/usage</code> and <code>/status</code> (prompt automation, safer command palette confirmation, partial UTF-8 handling, and parsing guards against status-bar context meters) (#320).</li>\n<li>New provider: Warp (credits + add-on credits) (#352). Thanks @Kathie-yu!</li>\n<li>Provider correctness fixes landed for Cursor plan parsing and MiniMax region routing (#240, #234, #344). Thanks @robinebers and @theglove44!</li>\n<li>Menu bar animation behavior was hardened in merged mode and fallback mode (#283, #291). Thanks @vignesh07 and @Ilakiancs!</li>\n<li>CI/tooling reliability improved via pinned lint tools, deterministic macOS test execution, and PTY timing test stabilization plus Node 24-ready GitHub Actions upgrades (#292, #312, #290).</li>\n</ul>\n<h3>Claude OAuth & Keychain</h3>\n<ul>\n<li>Claude OAuth creds are cached in CodexBar Keychain to reduce repeated prompts.</li>\n<li>Prompts can still appear when Claude OAuth credentials are expired, invalid, or missing and re-auth is required.</li>\n<li>In Auto mode, background refresh keeps prompts suppressed; interactive prompts are limited to user actions (menu open or manual refresh).</li>\n<li>OAuth-only mode remains strict (no silent Web/CLI fallback); Auto mode may do one delegated CLI refresh + one OAuth retry before falling back.</li>\n<li>Preferences now expose a Claude Keychain prompt policy (Never / Only on user action / Always allow prompts) under Providers → Claude; if global Keychain access is disabled in Advanced, this control remains visible but inactive.</li>\n</ul>\n<h3>Provider & Usage Fixes</h3>\n<ul>\n<li>Warp: add Warp provider support (credits + add-on credits), configurable via Settings or <code>WARP_API_KEY</code>/<code>WARP_TOKEN</code> (#352). Thanks @Kathie-yu!</li>\n<li>Cursor: compute usage against <code>plan.limit</code> rather than <code>breakdown.total</code> to avoid incorrect limit interpretation (#240). Thanks @robinebers!</li>\n<li>MiniMax: correct API region URL selection to route requests to the expected regional endpoint (#234). Thanks @theglove44!</li>\n<li>MiniMax: always show the API region picker and retry the China endpoint when the global host rejects the token to avoid upgrade regressions for users without a persisted region (#344). Thanks @apoorvdarshan!</li>\n<li>Claude: add Opus 4.6 pricing so token cost scanning tracks USD consumed correctly (#348). Thanks @arandaschimpf!</li>\n<li>z.ai: handle quota responses with missing token-limit fields, avoid incorrect used-percent calculations, and harden empty-response behavior with safer logging (#346). Thanks @MohamedMohana and @halilertekin!</li>\n<li>z.ai: fix provider visibility in the menu when enabled with token-account credentials (availability now considers the effective fetch environment).</li>\n<li>Amp: detect login redirects during usage fetch and fail fast when the session is invalid (#339). Thanks @JosephDoUrden!</li>\n<li>Resource loading: fix app bundle lookup path to avoid \"could not load resource bundle\" startup failures (#223). Thanks @validatedev!</li>\n<li>OpenAI Web dashboard: keep WebView instances cached for reuse to reduce repeated network fetch overhead; tests were updated to avoid network-dependent flakes (#284). Thanks @vignesh07!</li>\n<li>Token-account precedence: selected token account env injection now correctly overrides provider config <code>apiKey</code> values in app and CLI environments. Thanks @arvindcr4!</li>\n<li>Claude: make Claude CLI probing more resilient by scoping auto-input to the active subcommand and trimming to the latest Usage panel before parsing to avoid false matches from earlier screen fragments (#320).</li>\n</ul>\n<h3>Menu Bar & UI Behavior</h3>\n<ul>\n<li>Prevent fallback-provider loading animation loops (battery/CPU drain when no providers are enabled) (#283). Thanks @vignesh07!</li>\n<li>Prevent status overlay rendering for disabled providers while in merged mode (#291). Thanks @Ilakiancs!</li>\n</ul>\n<h3>CI, Tooling & Test Stability</h3>\n<ul>\n<li>Pin SwiftFormat/SwiftLint versions and harden lint installer behavior (version drift + temp-file leak fixes) (#292).</li>\n<li>Use more deterministic macOS CI test settings (including non-parallel paths where needed) and align runner/toolchain behavior for stability (#292).</li>\n<li>Stabilize PTY command timing tests to reduce CI flakiness (#312).</li>\n<li>Upgrade <code>actions/checkout</code> to v6 and <code>actions/github-script</code> to v8 for Node 24 compatibility in <code>upstream-monitor.yml</code> (#290). Thanks @salmanmkc!</li>\n<li>Tests: add TaskLocal-based keychain/cache overrides so keychain gating and KeychainCacheStore test stores do not leak across concurrent test execution (#320).</li>\n</ul>\n<h3>Docs & Maintenance</h3>\n<ul>\n<li>Update docs for Claude data fetch behavior and keychain troubleshooting notes.</li>\n<li>Update MIT license year.</li>\n</ul>\n<p><a href=\"https://github.com/steipete/CodexBar/blob/main/CHANGELOG.md\">View full changelog</a></p>\n]]></description>\n            <enclosure url=\"https://github.com/steipete/CodexBar/releases/download/v0.18.0-beta.3/CodexBar-0.18.0-beta.3.zip\" length=\"23436028\" type=\"application/octet-stream\" sparkle:edSignature=\"mb72DHg5xz+AY/DrCDPhpkWaRa24NwcYXnW5lKuYBzlF5kMAsqTZGaG82KWPY8su8J5bJ5wtOMjVdzPCJWjvAw==\"/>\n        </item>\n        <item sparkle:channel=\"beta\">\n            <title>0.18.0-beta.2</title>\n            <pubDate>Wed, 21 Jan 2026 08:42:37 +0000</pubDate>\n            <link>https://raw.githubusercontent.com/steipete/CodexBar/main/appcast.xml</link>\n            <sparkle:version>50</sparkle:version>\n            <sparkle:shortVersionString>0.18.0-beta.2</sparkle:shortVersionString>\n            <sparkle:minimumSystemVersion>14.0</sparkle:minimumSystemVersion>\n            <description><![CDATA[<h2>CodexBar 0.18.0-beta.2</h2>\n<h3>Highlights</h3>\n<ul>\n<li>OpenAI web dashboard refresh cadence now follows 5× the base refresh interval.</li>\n<li>OpenAI web dashboard WebView is torn down after each scrape to reduce idle CPU.</li>\n<li>Codex settings now include a toggle to disable OpenAI web extras.</li>\n</ul>\n<h3>Providers</h3>\n<ul>\n<li>Providers: add Dia browser support across cookie import and profile detection (#209). Thanks @validatedev!</li>\n<li>Codex: include archived session logs in local token cost scanning and dedupe by session id.</li>\n<li>Claude: harden CLI /usage parsing and avoid ANTHROPIC_* env interference during probes.</li>\n</ul>\n<h3>Menu & Menu Bar</h3>\n<ul>\n<li>Menu: opening OpenAI web submenus triggers a refresh when the data is stale.</li>\n<li>Menu: fix usage line labels to honor “Show usage as used”.</li>\n<li>Debug: add a toggle to keep Codex/Claude CLI sessions alive between probes.</li>\n<li>Debug: add a button to reset CLI probe sessions.</li>\n<li>App icon: use the classic icon on macOS 15 and earlier while keeping Liquid Glass for macOS 26+ (#178). Thanks @zerone0x!</li>\n</ul>\n<p><a href=\"https://github.com/steipete/CodexBar/blob/main/CHANGELOG.md\">View full changelog</a></p>\n]]></description>\n            <enclosure url=\"https://github.com/steipete/CodexBar/releases/download/v0.18.0-beta.2/CodexBar-0.18.0-beta.2.zip\" length=\"22498811\" type=\"application/octet-stream\" sparkle:edSignature=\"Zcy1PnvfyNQnpFvuydwnIQ+lUh1wGF46p92MyVeVAa/z441erCIHZ9bKH4QDlc9cB/QsTkYfk3CvE0lHTu5CCA==\"/>\n        </item>\n        <item>\n            <title>0.14.0</title>\n            <pubDate>Thu, 25 Dec 2025 03:56:15 +0100</pubDate>\n            <link>https://raw.githubusercontent.com/steipete/CodexBar/main/appcast.xml</link>\n            <sparkle:version>41</sparkle:version>\n            <sparkle:shortVersionString>0.14.0</sparkle:shortVersionString>\n            <sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>\n            <description><![CDATA[<h2>CodexBar 0.14.0</h2>\n<ul>\n<li>Antigravity: new local provider for the Antigravity language server (Claude + Gemini quotas) with an experimental toggle; improved plan display + debug output; clearer not-running/port errors; hide account switch.</li>\n<li>Status: poll Google Workspace incidents for Gemini + Antigravity; Status Page opens the Workspace status page.</li>\n            <li>Settings: add Providers tab; move cost usage + status toggles to General; keep display controls in Advanced.</li>\n<li>Menu/UI: widen the menu for four providers; cards/charts adapt to menu width; tighten provider switcher/toggle spacing; keep menus refreshed while open.</li>\n<li>Gemini: hide the dashboard action when unsupported.</li>\n<li>Claude: fix Extra usage spend/limit units (cents); improve CLI probe stability; surface web session info in Debug.</li>\n<li>OpenAI web: fix dashboard ghost overlay on desktop (WebKit keepalive window).</li>\n<li>Debug: add a debug-lldb build mode for troubleshooting.</li>\n</ul>\n<p><a href=\"https://github.com/steipete/CodexBar/blob/main/CHANGELOG.md\">View full changelog</a></p>\n]]></description>\n            <enclosure url=\"https://github.com/steipete/CodexBar/releases/download/v0.14.0/CodexBar-0.14.0.zip\" length=\"5382347\" type=\"application/octet-stream\" sparkle:edSignature=\"qhj8jnqH/eOXEP2plbZyTb8w1x0Afcpix0kC+pzMh5jX9Lkf3Yg2Bvd6/vsKdhOObEwJjQZRb/RKiSBx55J3DA==\"/>\n        </item>\n        <item>\n            <title>0.13.0</title>\n            <pubDate>Wed, 24 Dec 2025 01:53:35 +0100</pubDate>\n            <link>https://raw.githubusercontent.com/steipete/CodexBar/main/appcast.xml</link>\n            <sparkle:version>40</sparkle:version>\n            <sparkle:shortVersionString>0.13.0</sparkle:shortVersionString>\n            <sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>\n            <enclosure url=\"https://github.com/steipete/CodexBar/releases/download/v0.13.0/CodexBar-0.13.0.zip\" length=\"5099083\" type=\"application/octet-stream\" sparkle:edSignature=\"EmhEmcTFQEpuQG6vZ288l9j278VjnpQdQcmZnU1JDvzERVTtnIg+8RDlsHV9niwejaOBPX6mvXST3vNJHpwADA==\"/>\n        </item>\n        <item>\n            <title>0.12.0</title>\n            <pubDate>Tue, 23 Dec 2025 04:39:05 +0100</pubDate>\n            <link>https://raw.githubusercontent.com/steipete/CodexBar/main/appcast.xml</link>\n            <sparkle:version>39</sparkle:version>\n            <sparkle:shortVersionString>0.12.0</sparkle:shortVersionString>\n            <sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>\n            <enclosure url=\"https://github.com/steipete/CodexBar/releases/download/v0.12.0/CodexBar-0.12.0.zip\" length=\"4888386\" type=\"application/octet-stream\" sparkle:edSignature=\"tQMHO/RNAbvwRHXYnLAkNV2ksiV722qR8fEYzcbipgetacfPnwnLJ0Pe/lAiZ03PBmj3BkisHb74GosUlSV+DQ==\"/>\n        </item>\n    </channel>\n</rss>"
  },
  {
    "path": "bin/docs-list",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\nSCRIPT_DIR=$(cd \"$(dirname \"$0\")\" && pwd)\nPROJECT_ROOT=$(cd \"$SCRIPT_DIR/..\" && pwd)\nnode \"$PROJECT_ROOT/Scripts/docs-list.mjs\" \"$@\"\n"
  },
  {
    "path": "bin/install-codexbar-cli.sh",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\nAPP=\"/Applications/CodexBar.app\"\nHELPER=\"$APP/Contents/Helpers/CodexBarCLI\"\nTARGETS=(\"/usr/local/bin/codexbar\" \"/opt/homebrew/bin/codexbar\")\n\nif [[ ! -x \"$HELPER\" ]]; then\n  echo \"CodexBarCLI helper not found at $HELPER. Please reinstall CodexBar.\" >&2\n  exit 1\nfi\n\ninstall_script=$(mktemp)\ncat > \"$install_script\" <<'EOF'\n#!/usr/bin/env bash\nset -euo pipefail\nHELPER=\"__HELPER__\"\nTARGETS=(\"/usr/local/bin/codexbar\" \"/opt/homebrew/bin/codexbar\")\n\nfor t in \"${TARGETS[@]}\"; do\n  mkdir -p \"$(dirname \"$t\")\"\n  ln -sf \"$HELPER\" \"$t\"\n  echo \"Linked $t -> $HELPER\"\ndone\nEOF\n\nperl -pi -e \"s#__HELPER__#$HELPER#g\" \"$install_script\"\n\nosascript -e \"do shell script \\\"bash '$install_script'\\\" with administrator privileges\"\nrm -f \"$install_script\"\n\necho \"CodexBar CLI installed. Try: codexbar usage\"\n"
  },
  {
    "path": "docs/.nojekyll",
    "content": "\n"
  },
  {
    "path": "docs/CNAME",
    "content": "codexbar.app\n"
  },
  {
    "path": "docs/DEVELOPMENT.md",
    "content": "---\nsummary: \"Development workflow: build/run scripts, logging, and keychain migration notes.\"\nread_when:\n  - Starting local development\n  - Running build/test scripts\n  - Troubleshooting Keychain prompts in dev\n---\n\n# CodexBar Development Guide\n\n## Quick Start\n\n### Building and Running\n\n```bash\n# Full build, test, package, and launch (recommended)\n./Scripts/compile_and_run.sh\n\n# Just build and package (no tests)\n./Scripts/package_app.sh\n\n# Launch existing app (no rebuild)\n./Scripts/launch.sh\n```\n\n### Development Workflow\n\n1. **Make code changes** in `Sources/CodexBar/`\n2. **Run** `./Scripts/compile_and_run.sh` to rebuild and launch\n3. **Check logs** in Console.app (filter by \"codexbar\")\n4. **Optional file log**: enable Debug → Logging → \"Enable file logging\" to write\n   `~/Library/Logs/CodexBar/CodexBar.log` (verbosity defaults to \"Verbose\")\n\n## Keychain Prompts (Development)\n\n### First Launch After Fresh Clone\nYou'll see **one keychain prompt per stored credential** on the first launch. This is a **one-time migration** that converts existing keychain items to use `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly`.\n\n### Subsequent Rebuilds\n**Zero prompts!** The migration flag is stored in UserDefaults, so future rebuilds won't prompt.\n\n### Why This Happens\n- Ad-hoc signed development builds change code signature on every rebuild\n- macOS keychain normally prompts when signature changes\n- We use `ThisDeviceOnly` accessibility to prevent prompts\n- Migration runs once to convert any existing items\n\n### Reset Migration (Testing)\n```bash\ndefaults delete com.steipete.codexbar KeychainMigrationV1Completed\n```\n\n## Auto-Refresh for Augment Cookies\n\n### How It Works\nCodexBar automatically refreshes Augment cookies from your browser:\n\n1. **Automatic Import**: On every usage refresh, CodexBar imports fresh cookies from your browser\n2. **Browser Priority**: Chrome → Arc → Safari → Firefox → Brave (configurable)\n3. **Session Detection**: Looks for Auth0/NextAuth session cookies\n4. **Fallback**: If import fails, uses last known good cookies from keychain\n\n### Refresh Frequency\n- Default: Every 5 minutes (configurable in Preferences → General)\n- Minimum: 30 seconds\n- Cookie import happens automatically on each refresh\n\n### Supported Browsers\n- Chrome\n- Arc\n- Safari\n- Firefox\n- Brave\n- Edge\n\n### Manual Cookie Override\nIf automatic import fails:\n1. Open Preferences → Providers → Augment\n2. Change \"Cookie source\" to \"Manual\"\n3. Paste cookie header from browser DevTools\n\n## Project Structure\n\n```\nCodexBar/\n├── Sources/CodexBar/          # Main app (SwiftUI + AppKit)\n│   ├── CodexBarApp.swift      # App entry point\n│   ├── StatusItemController.swift  # Menu bar icon\n│   ├── UsageStore.swift       # Usage data management\n│   ├── SettingsStore.swift    # User preferences\n│   ├── Providers/             # Provider-specific code\n│   │   ├── Augment/           # Augment Code integration\n│   │   ├── Claude/            # Anthropic Claude\n│   │   ├── Codex/             # OpenAI Codex\n│   │   └── ...\n│   └── KeychainMigration.swift  # One-time keychain migration\n├── Sources/CodexBarCore/      # Shared business logic\n├── Tests/CodexBarTests/       # XCTest suite\n└── Scripts/                   # Build and packaging scripts\n```\n\n## Common Tasks\n\n### Add a New Provider\n1. Create `Sources/CodexBar/Providers/YourProvider/`\n2. Implement `ProviderImplementation` protocol\n3. Add to `ProviderRegistry.swift`\n4. Add icon to `Resources/ProviderIcon-yourprovider.svg`\n\n### Debug Cookie Issues\n```bash\n# Enable verbose logging\nexport CODEXBAR_LOG_LEVEL=debug\n./Scripts/compile_and_run.sh\n\n# Check logs in Console.app\n# Filter: subsystem:com.steipete.codexbar category:augment-cookie\n```\n\n### Run Tests Only\n```bash\nswift test\n```\n\n### Format Code\n```bash\nswiftformat Sources Tests\nswiftlint --strict\n```\n\n## Distribution\n\n### Local Development Build\n```bash\n./Scripts/package_app.sh\n# Creates: CodexBar.app (ad-hoc signed)\n```\n\n### Release Build (Notarized)\n```bash\n./Scripts/sign-and-notarize.sh\n# Creates: CodexBar-arm64.zip (notarized for distribution)\n```\n\nSee `docs/RELEASING.md` for full release process.\n\n## Troubleshooting\n\n### App Won't Launch\n```bash\n# Check crash logs\nls -lt ~/Library/Logs/DiagnosticReports/CodexBar* | head -5\n\n# Check Console.app for errors\n# Filter: process:CodexBar\n```\n\n### Keychain Prompts Keep Appearing\n```bash\n# Verify migration completed\ndefaults read com.steipete.codexbar KeychainMigrationV1Completed\n# Should output: 1\n\n# Check migration logs\nlog show --predicate 'category == \"KeychainMigration\"' --last 5m\n```\n\n### Cookies Not Refreshing\n1. Check browser is supported (Chrome, Arc, Safari, Firefox, Brave)\n2. Verify you're logged into Augment in that browser\n3. Check Preferences → Providers → Augment → Cookie source is \"Automatic\"\n4. Enable debug logging and check Console.app\n\n## Architecture Notes\n\n### Menu Bar App Pattern\n- No dock icon (LSUIElement = true)\n- Status item only (NSStatusBar)\n- SwiftUI for preferences, AppKit for menu\n- Hidden 1×1 window keeps SwiftUI lifecycle alive\n\n### Cookie Management\n- Automatic browser import via SweetCookieKit\n- Keychain storage for persistence\n- Manual override for debugging\n- Auto-refresh on every usage poll\n\n### Usage Polling\n- Background timer (configurable frequency)\n- Parallel provider fetches\n- Exponential backoff on errors\n- Widget snapshot for iOS widget\n"
  },
  {
    "path": "docs/DEVELOPMENT_SETUP.md",
    "content": "---\nsummary: \"Development setup: stable signing and reducing Keychain prompts.\"\nread_when:\n  - Setting up local development\n  - Reducing Keychain prompts during rebuilds\n  - Configuring dev signing\n---\n\n# Development Setup Guide\n\n## Reducing Keychain Permission Prompts\n\nWhen developing CodexBar, you may see frequent keychain permission prompts like:\n\n> **CodexBar wants to access key \"Claude Code-credentials\" in your keychain.**\n\nThis happens because each rebuild creates a new code signature, and macOS treats it as a \"different\" app.\n\n### Quick Fix (Temporary)\n\nWhen the prompt appears, click **\"Always Allow\"** instead of just \"Allow\". This grants access to the current build.\n\n### Permanent Fix (Recommended)\n\nUse a stable development certificate that doesn't change between rebuilds:\n\n#### 1. Create Development Certificate\n\n```bash\n./Scripts/setup_dev_signing.sh\n```\n\nThis creates a self-signed certificate named \"CodexBar Development\".\n\n#### 2. Trust the Certificate\n\n1. Open **Keychain Access.app**\n2. Find **\"CodexBar Development\"** in the **login** keychain\n3. Double-click it\n4. Expand the **\"Trust\"** section\n5. Set **\"Code Signing\"** to **\"Always Trust\"**\n6. Close the window (enter your password when prompted)\n\n#### 3. Configure Your Shell\n\nAdd this to your `~/.zshrc` (or `~/.bashrc` if using bash):\n\n```bash\nexport APP_IDENTITY='CodexBar Development'\n```\n\nThen restart your terminal:\n\n```bash\nsource ~/.zshrc\n```\n\n#### 4. Rebuild\n\n```bash\n./Scripts/compile_and_run.sh\n```\n\nNow your builds will use the stable certificate, and keychain prompts will be much less frequent!\n\n> Note: `compile_and_run.sh` now auto-detects a valid signing identity (Developer ID or CodexBar Development).\n> Set `APP_IDENTITY` to override the auto-detected choice.\n\n---\n\n## Cleaning Up Old App Bundles\n\nIf you see multiple `CodexBar *.app` bundles in your project directory, you can clean them up:\n\n```bash\n# Remove all numbered builds\nrm -rf \"CodexBar \"*.app\n\n# The .gitignore already excludes these patterns:\n# - CodexBar.app\n# - CodexBar *.app/\n```\n\nThe build script creates `CodexBar.app` in the project root. Old numbered builds (like `CodexBar 2.app`) are created when Finder can't overwrite the running app.\n\n---\n\n## Development Workflow\n\n### Standard Build & Run\n\n```bash\n./Scripts/compile_and_run.sh\n```\n\nThis script:\n1. Kills existing CodexBar instances\n2. Runs `swift build` (release mode)\n3. Runs `swift test` (all tests)\n4. Packages the app with `./Scripts/package_app.sh`\n5. Launches `CodexBar.app`\n6. Verifies it stays running\n\n### Quick Build (No Tests)\n\n```bash\nswift build -c release\n./Scripts/package_app.sh\n```\n\n### Run Tests Only\n\n```bash\nswift test\n```\n\n### Debug Build\n\n```bash\nswift build  # defaults to debug\n./Scripts/package_app.sh debug\n```\n\n---\n\n## Troubleshooting\n\n### \"CodexBar is already running\"\n\nThe compile_and_run script should kill old instances, but if it doesn't:\n\n```bash\npkill -x CodexBar || pkill -f CodexBar.app || true\n```\n\n### \"Permission denied\" when accessing keychain\n\nMake sure you clicked **\"Always Allow\"** or set up the development certificate (see above).\n\n### Multiple app bundles keep appearing\n\nThis happens when the running app locks the bundle. The compile_and_run script handles this by killing the app first.\n\nIf you still see old bundles:\n\n```bash\nrm -rf \"CodexBar \"*.app\n```\n\n### App doesn't reflect latest changes\n\nAlways rebuild and restart:\n\n```bash\n./Scripts/compile_and_run.sh\n```\n\nOr manually:\n\n```bash\n./Scripts/package_app.sh\npkill -x CodexBar || pkill -f CodexBar.app || true\nopen -n CodexBar.app\n```\n"
  },
  {
    "path": "docs/FORK_QUICK_START.md",
    "content": "---\nsummary: \"Fork quick start: differences, commands, and planned features.\"\nread_when:\n  - Onboarding to the fork workflow\n  - Reviewing fork-specific changes\n  - Running fork maintenance commands\n---\n\n# CodexBar Fork - Quick Start Guide\n\n**Fork Maintainer:** Brandon Charleson ([topoffunnel.com](https://topoffunnel.com))  \n**Original Author:** Peter Steinberger ([steipete](https://twitter.com/steipete))  \n**Fork Repository:** https://github.com/topoffunnel/CodexBar\n\n---\n\n## 🎯 What Makes This Fork Different?\n\n### Key Enhancements\n1. **Augment Provider Support** - Full integration with Augment Code API\n2. **Enhanced Security** - Improved keychain handling, no permission prompts\n3. **Better Cookie Management** - Automatic session keepalive, Chrome Beta support\n4. **Bug Fixes** - Cursor bonus credits, cookie domain filtering\n\n### Planned Features\n- Multi-account management per provider\n- Enhanced diagnostics and logging\n- Upstream sync automation\n- Usage history tracking\n\n---\n\n## 🚀 Quick Commands\n\n### Development\n```bash\n# Build and run (kills old instances, builds, tests, packages, relaunches)\n./Scripts/compile_and_run.sh\n\n# Quick build\nswift build\n\n# Run tests\nswift test\n\n# Format code\nswiftformat Sources Tests\nswiftlint --strict\n\n# Package app\n./Scripts/package_app.sh\n\n# Restart app after rebuild\npkill -x CodexBar || pkill -f CodexBar.app || true\ncd /Users/steipete/Projects/codexbar && open -n /Users/steipete/Projects/codexbar/CodexBar.app\n```\n\n### Release\n```bash\n# Sign and notarize (keep in foreground!)\n./Scripts/sign-and-notarize.sh\n\n# Create appcast\n./Scripts/make_appcast.sh <zip> <feed-url>\n\n# See full release process\ncat docs/RELEASING.md\n```\n\n### Git Workflow\n```bash\n# Check status\ngit status\n\n# Create feature branch\ngit checkout -b feature/my-feature\n\n# Commit changes\ngit add -A\ngit commit -m \"feat: description\"\n\n# Push to fork\ngit push origin feature/my-feature\n\n# Sync with upstream (TBD - see docs/FORK_ROADMAP.md Phase 4)\n```\n\n---\n\n## 📁 Key Files & Directories\n\n### Source Code\n- `Sources/CodexBar/` - Swift 6 menu bar app\n- `Sources/CodexBarCore/` - Core logic, providers, utilities\n- `Sources/CodexBarCore/Providers/Augment/` - Augment provider implementation\n- `Tests/CodexBarTests/` - XCTest coverage\n\n### Scripts\n- `Scripts/compile_and_run.sh` - Main development script\n- `Scripts/package_app.sh` - Package app bundle\n- `Scripts/sign-and-notarize.sh` - Release signing\n- `Scripts/make_appcast.sh` - Generate appcast XML\n\n### Documentation\n- `docs/augment.md` - Augment provider guide\n- `docs/FORK_ROADMAP.md` - Development roadmap\n- `docs/RELEASING.md` - Release process\n- `docs/DEVELOPMENT.md` - Build instructions\n- `README.md` - Main documentation\n\n---\n\n## 🔧 Common Tasks\n\n### Adding a New Feature\n1. Create feature branch: `git checkout -b feature/my-feature`\n2. Make changes in `Sources/`\n3. Add tests in `Tests/`\n4. Run `./Scripts/compile_and_run.sh` to verify\n5. Run `swiftformat Sources Tests && swiftlint --strict`\n6. Commit with descriptive message\n7. Push and create PR\n\n### Debugging Augment Issues\n1. Enable debug logging: `export CODEXBAR_LOG_LEVEL=debug`\n2. Check Console.app for \"com.steipete.codexbar\"\n3. Use Settings → Debug → Augment → Show Debug Info\n4. Check `docs/augment.md` troubleshooting section\n\n### Testing Changes\n```bash\n# Run all tests\nswift test\n\n# Run specific test\nswift test --filter AugmentTests\n\n# Build and test together\n./Scripts/compile_and_run.sh\n```\n\n### Updating Documentation\n1. Edit relevant `.md` file in `docs/`\n2. Update `README.md` if needed\n3. Commit with `docs:` prefix\n4. No need to rebuild app\n\n---\n\n## 🐛 Troubleshooting\n\n### App Won't Launch\n```bash\n# Kill all instances\npkill -x CodexBar || pkill -f CodexBar.app || true\n\n# Rebuild and relaunch\n./Scripts/compile_and_run.sh\n```\n\n### Build Errors\n```bash\n# Clean build\nswift package clean\nswift build\n\n# Check for format issues\nswiftformat Sources Tests --lint\nswiftlint --strict\n```\n\n### Cookie Issues (Augment)\n1. Check browser is logged into app.augmentcode.com\n2. Verify cookie source in Settings → Providers → Augment\n3. Try manual cookie import (see `docs/augment.md`)\n4. Check debug logs for cookie import details\n\n### Keychain Permission Prompts\n- This fork includes fixes to eliminate prompts\n- If you still see prompts, check `Sources/CodexBarCore/Keychain/`\n- Ensure you're running the latest build\n\n---\n\n## 📚 Learning Resources\n\n### Understanding the Codebase\n1. Start with `Sources/CodexBar/CodexbarApp.swift` - App entry point\n2. Review `Sources/CodexBarCore/UsageStore.swift` - Main state management\n3. Check `Sources/CodexBarCore/Providers/` - Provider implementations\n4. Read `docs/provider.md` - Provider authoring guide\n\n### Swift 6 & SwiftUI\n- Uses `@Observable` macro (not `ObservableObject`)\n- Prefer `@State` ownership over `@StateObject`\n- Use `@Bindable` in views for two-way binding\n- Strict concurrency checking enabled\n\n### Coding Style\n- 4-space indentation\n- 120-character line limit\n- Explicit `self` is intentional (don't remove)\n- Follow existing `MARK` organization\n- Use descriptive variable names\n\n---\n\n## 🤝 Contributing\n\n### To This Fork\n1. Fork the fork repository\n2. Create feature branch\n3. Make changes with tests\n4. Submit PR to `topoffunnel/CodexBar`\n\n### To Upstream\n1. Check if feature benefits all users\n2. Create PR to `steipete/CodexBar`\n3. Reference this fork if relevant\n4. Be patient with review process\n\nSee `docs/FORK_ROADMAP.md` for contribution strategy.\n\n---\n\n## 📞 Support\n\n### Fork-Specific Issues\n- GitHub Issues: https://github.com/topoffunnel/CodexBar/issues\n- Email: [your-email]@topoffunnel.com\n\n### Upstream Issues\n- GitHub Issues: https://github.com/steipete/CodexBar/issues\n- Twitter: [@steipete](https://twitter.com/steipete)\n\n---\n\n## 📋 Next Steps\n\n1. **Read the Roadmap:** `docs/FORK_ROADMAP.md`\n2. **Set Up Development:** `./Scripts/compile_and_run.sh`\n3. **Review Augment Docs:** `docs/augment.md`\n4. **Check Current Issues:** GitHub Issues tab\n5. **Join Development:** Pick a task from Phase 2-5\n\n---\n\n## 🎉 Quick Wins\n\nWant to contribute but not sure where to start? Try these:\n\n- [ ] Add more test coverage for Augment provider\n- [ ] Improve error messages in cookie import\n- [ ] Add screenshots to `docs/augment.md`\n- [ ] Test on different macOS versions\n- [ ] Report bugs you find\n- [ ] Suggest UI improvements\n\nHappy coding! 🚀\n"
  },
  {
    "path": "docs/FORK_ROADMAP.md",
    "content": "---\nsummary: \"Fork roadmap: phases, milestones, and planned improvements.\"\nread_when:\n  - Planning fork work\n  - Reviewing fork milestones\n---\n\n# CodexBar Fork Roadmap\n\nThis document outlines the development roadmap for the CodexBar fork maintained by Brandon Charleson.\n\n## ✅ Phase 1: Fork Identity (COMPLETE)\n\n**Status:** Completed Jan 4, 2026\n\n**Achievements:**\n- Established dual attribution in About section\n- Updated README with fork notice and enhancements\n- Created comprehensive Augment provider documentation\n- App builds and runs successfully\n\n**Commit:** `da3d13e` - \"feat: establish fork identity with dual attribution\"\n\n---\n\n## 🔧 Phase 2: Enhanced Augment Diagnostics\n\n**Goal:** Fix persistent cookie disconnection issues with better logging and diagnostics\n\n**Tasks:**\n1. **Replace print() with proper logging**\n   - Use `CodexBarLog.logger(\"augment\")` throughout\n   - Add structured metadata for debugging\n   - Follow patterns from Claude/Cursor providers\n\n2. **Enhanced Cookie Diagnostics**\n   - Log cookie expiration times\n   - Track cookie refresh attempts\n   - Add cookie domain filtering diagnostics\n   - Log browser source priority\n\n3. **Session Keepalive Monitoring**\n   - Add keepalive status to debug pane\n   - Log refresh attempts and success/failure\n   - Track time until next refresh\n   - Add manual \"Force Refresh\" button\n\n4. **Debug Pane Improvements**\n   - Add \"Cookie Status\" section showing:\n     - Current cookies and expiration\n     - Last successful import\n     - Browser source used\n     - Keepalive status\n   - Add \"Test Connection\" button\n   - Show detailed error messages\n\n**Files to Modify:**\n- `Sources/CodexBarCore/Providers/Augment/AugmentStatusProbe.swift`\n- `Sources/CodexBarCore/Providers/Augment/AugmentSessionKeepalive.swift`\n- `Sources/CodexBar/UsageStore.swift` (debug pane)\n\n---\n\n## 🎯 Phase 3: Quotio Feature Analysis\n\n**Goal:** Identify and cherry-pick valuable features from Quotio without copying code\n\n**Analysis Areas:**\n1. **Multi-Account Management**\n   - How Quotio handles multiple accounts per provider\n   - Account switching UI patterns\n   - Account status indicators\n\n2. **OAuth Flow Improvements**\n   - Quotio's OAuth implementation patterns\n   - Token refresh mechanisms\n   - Error handling strategies\n\n3. **UI/UX Patterns**\n   - Menu bar organization\n   - Settings layout\n   - Status indicators\n   - Notification patterns\n\n4. **Session Management**\n   - How Quotio handles session persistence\n   - Cookie refresh strategies\n   - Automatic reconnection logic\n\n**Deliverable:** `docs/QUOTIO_ANALYSIS.md` with:\n- Feature comparison matrix\n- Implementation recommendations\n- Priority ranking\n- Effort estimates\n\n---\n\n## 🔄 Phase 4: Upstream Sync Workflow\n\n**Goal:** Set up automated workflow to sync with upstream while maintaining fork changes\n\n**Tasks:**\n1. **Create Sync Script**\n   - `Scripts/sync_upstream.sh`\n   - Fetch upstream changes\n   - Show diff summary\n   - Interactive merge/rebase\n\n2. **Conflict Resolution Guide**\n   - Document common conflict areas\n   - Resolution strategies\n   - Testing checklist\n\n3. **Automated Checks**\n   - CI workflow to detect upstream changes\n   - Weekly sync reminders\n   - Compatibility testing\n\n**Files to Create:**\n- `Scripts/sync_upstream.sh`\n- `docs/UPSTREAM_SYNC.md`\n- `.github/workflows/upstream-sync-check.yml`\n\n---\n\n## 🚀 Phase 5: Multi-Account Management Foundation\n\n**Goal:** Implement multi-account support for providers (starting with Augment)\n\n**Features:**\n1. **Account Management UI**\n   - Add/remove accounts per provider\n   - Account nicknames/labels\n   - Active account indicator\n   - Quick account switching\n\n2. **Account Storage**\n   - Keychain-based account storage\n   - Account metadata (email, plan, last used)\n   - Secure credential isolation\n\n3. **Account Switching**\n   - Switch active account from menu\n   - Preserve per-account usage history\n   - Automatic account selection based on quota\n\n4. **UI Enhancements**\n   - Account dropdown in menu bar\n   - Per-account usage display\n   - Account health indicators\n\n**Implementation Plan:**\n1. Start with Augment provider (already has cookie infrastructure)\n2. Create `AccountManager` service\n3. Update `UsageStore` to handle multiple accounts\n4. Add account switcher to menu bar\n5. Extend to other providers (Claude, Cursor, etc.)\n\n**Files to Create:**\n- `Sources/CodexBarCore/AccountManager.swift`\n- `Sources/CodexBarCore/Providers/Augment/AugmentAccountManager.swift`\n- `Sources/CodexBar/AccountSwitcherView.swift`\n\n---\n\n## 📋 Future Enhancements\n\n### Short Term (1-2 weeks)\n- [ ] Augment cookie issue resolution (Phase 2)\n- [ ] Quotio feature analysis (Phase 3)\n- [ ] Upstream sync workflow (Phase 4)\n\n### Medium Term (1-2 months)\n- [ ] Multi-account management (Phase 5)\n- [ ] Enhanced notification system\n- [ ] Usage history tracking\n- [ ] Export usage data\n\n### Long Term (3+ months)\n- [ ] Custom provider API\n- [ ] Usage predictions/alerts\n- [ ] Cost optimization suggestions\n- [ ] Team usage aggregation\n\n---\n\n## 🤝 Upstream Contribution Strategy\n\n**When to Contribute Upstream:**\n- Bug fixes that benefit all users\n- Provider improvements (non-fork-specific)\n- Documentation improvements\n- Performance optimizations\n\n**When to Keep in Fork:**\n- Multi-account management (major architectural change)\n- Fork-specific branding/attribution\n- Experimental features\n- Features specific to topoffunnel.com users\n\n**PR Guidelines:**\n- Keep PRs focused and small\n- Include comprehensive tests\n- Follow upstream coding style\n- Document breaking changes\n- Be patient with review process\n\n---\n\n## 📊 Success Metrics\n\n**Technical:**\n- Zero cookie disconnection issues\n- < 1 second menu bar response time\n- 100% test coverage for new features\n- Zero regressions from upstream syncs\n\n**User:**\n- Positive feedback from topoffunnel.com users\n- Active usage metrics\n- Feature requests and engagement\n- Community contributions\n\n---\n\n## 🔗 Related Documentation\n\n- [Augment Provider](augment.md) - Augment-specific documentation\n- [Development Guide](DEVELOPMENT.md) - Build and test instructions\n- [Provider Authoring](provider.md) - How to create new providers\n- [Upstream Sync](UPSTREAM_SYNC.md) - Syncing with original repository (TBD)\n- [Quotio Analysis](QUOTIO_ANALYSIS.md) - Feature comparison (TBD)\n"
  },
  {
    "path": "docs/FORK_SETUP.md",
    "content": "---\nsummary: \"Fork setup: remote configuration and multi-upstream workflow.\"\nread_when:\n  - Setting up fork remotes\n  - Syncing with upstreams\n---\n\n# Fork Setup & Initial Configuration\n\n**One-time setup for managing your CodexBar fork with multiple upstreams**\n\n---\n\n## 🎯 Quick Setup\n\n### Step 1: Configure Git Remotes\n\n```bash\n# Verify your fork is origin\ngit remote -v\n# Should show: origin  git@github.com:topoffunnel/CodexBar.git\n\n# Add upstream (steipete's original)\ngit remote add upstream https://github.com/steipete/CodexBar.git\n\n# Add quotio (inspiration source)\ngit remote add quotio https://github.com/nguyenphutrong/quotio.git\n\n# Fetch all remotes\ngit fetch --all\n\n# Verify setup\ngit remote -v\n# Should show:\n# origin    git@github.com:topoffunnel/CodexBar.git (fetch/push)\n# upstream  https://github.com/steipete/CodexBar.git (fetch/push)\n# quotio    https://github.com/nguyenphutrong/quotio.git (fetch/push)\n```\n\n### Step 2: Test Automation Scripts\n\n```bash\n# Make scripts executable (if not already)\nchmod +x Scripts/*.sh\n\n# Test upstream monitoring\n./Scripts/check_upstreams.sh\n\n# Should show:\n# - Number of new commits in upstream\n# - Number of new commits in quotio\n# - File change summary\n```\n\n### Step 3: Initial Upstream Review\n\n```bash\n# Check what's new in upstream\n./Scripts/check_upstreams.sh upstream\n\n# Review changes in detail\n./Scripts/review_upstream.sh upstream\n\n# This creates a review branch: upstream-sync/upstream-YYYYMMDD\n```\n\n### Step 4: Initial Quotio Analysis\n\n```bash\n# Analyze quotio repository\n./Scripts/analyze_quotio.sh\n\n# Creates: quotio-analysis-YYYYMMDD.md\n# Review the file for interesting patterns\n```\n\n---\n\n## ⚠️ Critical Discovery: Upstream Removed Augment\n\n**IMPORTANT:** Upstream (steipete) has removed the Augment provider in recent commits!\n\n```\nFiles changed:\n .../Providers/Augment/AugmentStatusProbe.swift     | 627 deletions\n Tests/CodexBarTests/AugmentStatusProbeTests.swift  |  88 deletions\n```\n\n**This validates our fork strategy:**\n- ✅ Your fork preserves Augment support\n- ✅ You can continue developing Augment features\n- ✅ Upstream changes won't break your Augment work\n- ✅ You maintain features important to your users\n\n**Action Required:**\nWhen syncing with upstream, you'll need to:\n1. Cherry-pick valuable changes (Vertex AI improvements, bug fixes)\n2. **Avoid** merging commits that remove Augment\n3. Keep your Augment implementation separate\n\n---\n\n## 🔄 Regular Workflow\n\n### Weekly Upstream Check (Recommended: Monday)\n\n```bash\n# Check for new changes\n./Scripts/check_upstreams.sh\n\n# If changes found, review them\n./Scripts/review_upstream.sh upstream\n\n# Cherry-pick valuable commits (skip Augment removal)\ngit cherry-pick <commit-hash>\n\n# Test\n./Scripts/compile_and_run.sh\n\n# Merge to main\ngit checkout main\ngit merge upstream-sync/upstream-$(date +%Y%m%d)\n```\n\n### Weekly Quotio Review (Recommended: Thursday)\n\n```bash\n# Analyze recent quotio changes\n./Scripts/analyze_quotio.sh\n\n# Review specific files of interest\ngit show quotio/main:path/to/interesting/file.swift\n\n# Document patterns in docs/QUOTIO_ANALYSIS.md\n```\n\n---\n\n## 📋 Selective Sync Strategy\n\n### What to Sync from Upstream\n\n✅ **DO sync:**\n- Bug fixes (non-Augment)\n- Performance improvements\n- New provider support (Vertex AI, etc.)\n- Documentation improvements\n- Test improvements\n- Dependency updates\n\n❌ **DON'T sync:**\n- Augment provider removal\n- Changes that conflict with fork features\n- Breaking changes without careful review\n\n### How to Cherry-Pick Selectively\n\n```bash\n# Review upstream commits\ngit log --oneline main..upstream/main\n\n# Example output:\n# 001019c style: fix swiftformat violations ✅ SYNC\n# e4f1e4c feat(vertex): add token cost tracking ✅ SYNC\n# 202efde fix(vertex): disable double-counting ✅ SYNC\n# 0c2f888 docs: add Vertex AI documentation ✅ SYNC\n# 3c4ca30 feat(vertexai): token cost tracking ✅ SYNC\n# abc123d refactor: remove Augment provider ❌ SKIP\n\n# Cherry-pick the good ones\ngit cherry-pick 001019c\ngit cherry-pick e4f1e4c\ngit cherry-pick 202efde\ngit cherry-pick 0c2f888\ngit cherry-pick 3c4ca30\n# Skip abc123d (Augment removal)\n```\n\n---\n\n## 🎨 Quotio Pattern Learning\n\n### Ethical Guidelines\n\n**DO:**\n- ✅ Analyze their architecture and patterns\n- ✅ Learn from their UX decisions\n- ✅ Understand their approach to problems\n- ✅ Implement similar concepts independently\n- ✅ Credit inspiration in commits\n\n**DON'T:**\n- ❌ Copy code verbatim\n- ❌ Use their assets or branding\n- ❌ Violate their license\n- ❌ Claim their work as yours\n\n### Analysis Workflow\n\n```bash\n# 1. Fetch latest quotio\ngit fetch quotio\n\n# 2. Analyze structure\n./Scripts/analyze_quotio.sh\n\n# 3. Review specific areas\ngit show quotio/main:path/to/AccountManager.swift\n\n# 4. Document patterns (not code!)\n# Edit docs/QUOTIO_ANALYSIS.md\n\n# 5. Implement independently\n# Create feature branch\ngit checkout -b quotio-inspired/multi-account\n\n# 6. Commit with attribution\ngit commit -m \"feat: multi-account management\n\nInspired by quotio's account switching pattern:\nhttps://github.com/nguyenphutrong/quotio/...\n\nImplemented independently using CodexBar architecture.\"\n```\n\n---\n\n## 🚀 Contributing to Upstream\n\n### When to Contribute\n\n**Good candidates:**\n- Universal bug fixes\n- Performance improvements\n- Documentation improvements\n- Test coverage\n- Provider enhancements (non-fork-specific)\n\n**Keep in fork:**\n- Augment provider (they removed it)\n- Multi-account management (major change)\n- Fork branding\n- Experimental features\n\n### Contribution Workflow\n\n```bash\n# 1. Prepare clean branch from upstream\n./Scripts/prepare_upstream_pr.sh fix-cursor-bonus\n\n# 2. Cherry-pick your fix (without fork branding)\ngit cherry-pick <your-commit-hash>\n\n# 3. Review - ensure no fork-specific code\ngit diff upstream/main\n\n# 4. Test\nswift test\n\n# 5. Push to your fork\ngit push origin upstream-pr/fix-cursor-bonus\n\n# 6. Create PR on GitHub\n# Go to: https://github.com/steipete/CodexBar\n# Click \"New Pull Request\"\n# Select: base: steipete:main <- compare: topoffunnel:upstream-pr/fix-cursor-bonus\n```\n\n---\n\n## 🤖 Automated Monitoring\n\n### GitHub Actions Setup\n\nThe workflow `.github/workflows/upstream-monitor.yml` will:\n- Run Monday and Thursday at 9 AM UTC\n- Check for new commits in both upstreams\n- Create/update GitHub issue with summary\n- Provide links to review changes\n\n**To enable:**\n1. Push the workflow file to your fork\n2. Enable GitHub Actions in repository settings\n3. Issues will be created automatically\n\n**Manual trigger:**\n```bash\n# Via GitHub UI: Actions → Monitor Upstream Changes → Run workflow\n```\n\n---\n\n## 📊 Verification Checklist\n\nAfter setup, verify:\n\n- [ ] All three remotes configured (origin, upstream, quotio)\n- [ ] Scripts are executable\n- [ ] `./Scripts/check_upstreams.sh` runs successfully\n- [ ] Can create review branch with `./Scripts/review_upstream.sh`\n- [ ] Can analyze quotio with `./Scripts/analyze_quotio.sh`\n- [ ] GitHub Actions workflow is present\n- [ ] Understand Augment removal in upstream\n- [ ] Know how to cherry-pick selectively\n- [ ] Know when to contribute upstream vs keep in fork\n\n---\n\n## 🔗 Next Steps\n\n1. **Review Current Upstream Changes**\n   ```bash\n   ./Scripts/review_upstream.sh upstream\n   ```\n\n2. **Decide on Sync Strategy**\n   - Which commits to cherry-pick?\n   - How to handle Augment removal?\n   - See `docs/UPSTREAM_STRATEGY.md`\n\n3. **Start Quotio Analysis**\n   ```bash\n   ./Scripts/analyze_quotio.sh\n   # Then edit docs/QUOTIO_ANALYSIS.md\n   ```\n\n4. **Update Fork Roadmap**\n   - Review `docs/FORK_ROADMAP.md`\n   - Adjust based on upstream changes\n   - Plan fork-specific features\n\n---\n\n**Setup Complete!** You now have a robust system for managing your fork while learning from multiple sources.\n"
  },
  {
    "path": "docs/KEYCHAIN_FIX.md",
    "content": "---\nsummary: \"Current keychain behavior: legacy migration, Claude OAuth keychain bootstrap, and prompt mitigation.\"\nread_when:\n  - Investigating Keychain prompts\n  - Auditing Claude OAuth keychain behavior\n  - Comparing legacy keychain docs vs current architecture\n---\n\n# Keychain Fix: Current State\n\n## Scope change from the original doc\nThe original fix (migrating legacy CodexBar keychain items to `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly`) is\nstill in place, but the architecture has changed:\n\n- Provider settings and manual secrets are now persisted in `~/.codexbar/config.json`.\n- Legacy keychain stores are still present mainly to migrate old installs, then clear old items.\n- Keychain is still used for runtime cache entries (for example `com.steipete.codexbar.cache`) and Claude OAuth\n  bootstrap reads from Claude CLI keychain (`Claude Code-credentials`).\n\n## Then vs now\n\n| Previous statement in this doc | Current behavior |\n| --- | --- |\n| CodexBar stores provider credentials only in keychain | Manual/provider settings are config-file backed (`~/.codexbar/config.json`), while keychain is still used for runtime caches and Claude OAuth bootstrap fallback. |\n| `ClaudeOAuthCredentials.swift` migrated CodexBar-owned Claude OAuth keychain items | Claude OAuth primary source is Claude CLI keychain service (`Claude Code-credentials`), with CodexBar cache in `com.steipete.codexbar.cache` (`oauth.claude`). |\n| Migration runs in `CodexBarApp.init()` | Migration runs in `HiddenWindowView` `.task` via detached task (`KeychainMigration.migrateIfNeeded()`). |\n| Post-migration prompts should be zero in all Claude paths | Legacy-store prompts are reduced; Claude OAuth bootstrap can still prompt when reading Claude CLI keychain, with cooldown + no-UI probes to prevent storms. |\n| Log category is `KeychainMigration` | Category is `keychain-migration` (kebab-case). |\n\n## Current keychain surfaces for Claude\n\n### 1. Legacy CodexBar keychain migration (V1)\n`Sources/CodexBar/KeychainMigration.swift` migrates legacy `com.steipete.CodexBar` items (for example\n`claude-cookie`) to `AfterFirstUnlockThisDeviceOnly`.\n\n- Gate key: `KeychainMigrationV1Completed`\n- Runs once unless flag is reset.\n- Covers legacy CodexBar-managed accounts only (not Claude CLI's own keychain service).\n\n### 2. Claude OAuth bootstrap path\n`Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthCredentials.swift`\n\nLoad order for credentials:\n1. Environment override (`CODEXBAR_CLAUDE_OAUTH_TOKEN`, scopes env key).\n2. In-memory cache.\n3. CodexBar keychain cache (`com.steipete.codexbar.cache`, account `oauth.claude`).\n4. `~/.claude/.credentials.json`.\n5. Claude CLI keychain service: `Claude Code-credentials` (promptable fallback).\n\nPrompt mitigation:\n- Non-interactive keychain probes use `KeychainNoUIQuery` with `LAContext.interactionNotAllowed`.\n- Pre-alert is shown only when preflight suggests interaction may be required.\n- Denials are cooled down in the background via `claudeOAuthKeychainDeniedUntil`\n  (`ClaudeOAuthKeychainAccessGate`). User actions (menu open / manual refresh) clear this cooldown.\n- Auto-mode availability checks use non-interactive loads with prompt cooldown respected.\n- Background cache-sync-on-change also performs non-interactive Claude keychain probes (`syncWithClaudeKeychainIfChanged`)\n  and can update cached OAuth data when the token changes.\n\n### Why two Claude keychain prompts can still happen on startup\nWhen CodexBar does not have usable OAuth credentials in its own cache (`com.steipete.codexbar.cache` / `oauth.claude`),\nbootstrap falls through to Claude CLI keychain reads.\n\nCurrent flow can perform up to two interactive reads in one bootstrap call:\n1. Interactive read of the newest discovered keychain candidate.\n2. If that does not return usable data, interactive legacy service-level fallback read.\n\nOn some macOS keychain/ACL states, pressing **Allow** (session-only) for the first read does not grant enough access\nfor the second read shape, so macOS prompts again. Pressing **Always Allow** usually authorizes both query shapes for\nthe app identity and avoids the immediate second prompt.\n\nThe prompt copy differs because Security.framework is authorizing different operations:\n- one path is a direct secret-data read for the key item,\n- the fallback path is a key/service access query.\n\nThis is OS/keychain ACL behavior, not a `ThisDeviceOnly` migration issue.\n\n### 3. Claude web cookie cache\n`Sources/CodexBarCore/CookieHeaderCache.swift` and `Sources/CodexBarCore/KeychainCacheStore.swift`\n\n- Browser-imported Claude session cookies are cached in keychain service `com.steipete.codexbar.cache`.\n- Account key is `cookie.claude`.\n- Cache writes use `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly`.\n\n## What still uses `ThisDeviceOnly`\n\n- Legacy store implementations (`CookieHeaderStore`, token stores, MiniMax stores) still write using\n  `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly`.\n- Keychain cache store (`com.steipete.codexbar.cache`) also writes with `ThisDeviceOnly`.\n\n## Disable keychain access behavior\n\n`Advanced -> Disable Keychain access` sets `debugDisableKeychainAccess` and flips `KeychainAccessGate.isDisabled`.\n\nEffects:\n- Blocks keychain reads/writes in legacy stores.\n- Disables keychain-backed cookie auto-import paths.\n- Forces cookie source resolution to manual/off where applicable.\n\n## Verification\n\n### Check legacy migration flag\n```bash\ndefaults read com.steipete.codexbar KeychainMigrationV1Completed\n```\n\n### Check Claude OAuth keychain cooldown\n```bash\ndefaults read com.steipete.codexbar claudeOAuthKeychainDeniedUntil\n```\n\n### Inspect keychain-related logs\n```bash\nlog show --predicate 'subsystem == \"com.steipete.codexbar\" && (category == \"keychain-migration\" || category == \"keychain-preflight\" || category == \"keychain-prompt\" || category == \"keychain-cache\" || category == \"claude-usage\" || category == \"cookie-cache\")' --last 10m\n```\n\n### Reset migration for local testing\n```bash\ndefaults delete com.steipete.codexbar KeychainMigrationV1Completed\n./Scripts/compile_and_run.sh\n```\n\n## Key files (current)\n\n- `Sources/CodexBar/KeychainMigration.swift`\n- `Sources/CodexBar/HiddenWindowView.swift`\n- `Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthCredentials.swift`\n- `Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthKeychainAccessGate.swift`\n- `Sources/CodexBarCore/KeychainAccessPreflight.swift`\n- `Sources/CodexBarCore/KeychainNoUIQuery.swift`\n- `Sources/CodexBarCore/KeychainCacheStore.swift`\n- `Sources/CodexBarCore/CookieHeaderCache.swift`\n"
  },
  {
    "path": "docs/QUOTIO_ANALYSIS.md",
    "content": "---\nsummary: \"Quotio analysis: UX and architecture patterns for inspiration.\"\nread_when:\n  - Evaluating external inspiration\n  - Planning UX or architecture improvements\n---\n\n# Quotio Analysis & Pattern Adaptation\n\n**Purpose:** Learn from quotio's implementation patterns without copying code  \n**Repository:** https://github.com/nguyenphutrong/quotio  \n**Approach:** Analyze patterns, implement independently\n\n---\n\n## 🎯 Analysis Goals\n\n### What We're Looking For\n1. **UI/UX Patterns** - Menu organization, settings layout, status displays\n2. **Multi-Account Management** - How they handle multiple accounts per provider\n3. **Session Management** - Cookie handling, OAuth flows, session persistence\n4. **Provider Architecture** - How providers are structured and managed\n5. **Error Handling** - User-friendly error messages and recovery\n6. **Performance Optimizations** - Caching, background updates, efficiency\n\n### What We're NOT Doing\n- ❌ Copying code verbatim\n- ❌ Replicating their exact UI\n- ❌ Using their assets or branding\n- ❌ Violating their license\n\n### What We ARE Doing\n- ✅ Learning from their architectural decisions\n- ✅ Understanding their UX patterns\n- ✅ Adapting concepts to CodexBar conventions\n- ✅ Implementing independently with our own code\n- ✅ Crediting inspiration appropriately\n\n---\n\n## 🔍 Analysis Process\n\n### Step 1: Repository Overview\n\n```bash\n# Fetch latest quotio\n./Scripts/analyze_quotio.sh\n\n# Review file structure\ngit ls-tree -r --name-only quotio/main | grep -E '\\.(swift|md)$'\n\n# Check recent activity\ngit log --oneline --graph quotio/main --since=\"30 days ago\"\n```\n\n### Step 2: Feature Comparison\n\nCreate a comparison matrix:\n\n| Feature | CodexBar | Quotio | Notes |\n|---------|----------|--------|-------|\n| Multi-account | ❌ | ✅ | Priority for fork |\n| Provider count | 5 | ? | Check quotio |\n| Cookie import | ✅ | ? | Compare approaches |\n| OAuth support | ✅ | ? | Compare flows |\n| Session keepalive | ✅ | ? | Compare strategies |\n| Menu bar UI | Basic | ? | Compare organization |\n| Settings UI | Tabs | ? | Compare layout |\n\n### Step 3: Deep Dive Areas\n\n#### Multi-Account Management\n```bash\n# Find account-related files\ngit ls-tree -r --name-only quotio/main | grep -i account\n\n# View implementation (read-only)\ngit show quotio/main:path/to/AccountManager.swift\n\n# Document patterns in this file (see below)\n```\n\n**Questions to Answer:**\n- How are accounts stored? (Keychain, file, database?)\n- How is the active account selected?\n- How does UI show multiple accounts?\n- How are credentials isolated per account?\n- How does account switching work?\n\n#### Session Management\n```bash\n# Find session-related files\ngit ls-tree -r --name-only quotio/main | grep -iE '(session|cookie|auth)'\n\n# Review implementation\ngit show quotio/main:path/to/SessionManager.swift\n```\n\n**Questions to Answer:**\n- How are cookies refreshed?\n- How is session expiration detected?\n- How are multiple sessions managed?\n- What's the keepalive strategy?\n- How are errors handled?\n\n#### UI/UX Patterns\n```bash\n# Find UI files\ngit ls-tree -r --name-only quotio/main | grep -iE '(view|menu|ui)'\n\n# Review layouts\ngit show quotio/main:path/to/MenuBarView.swift\n```\n\n**Questions to Answer:**\n- How is the menu bar organized?\n- How are multiple accounts displayed?\n- What status indicators are used?\n- How are settings organized?\n- What's the navigation pattern?\n\n---\n\n## 📊 Findings Template\n\n### Feature: [Feature Name]\n\n**Quotio Approach:**\n- [Describe their implementation pattern]\n- [Key architectural decisions]\n- [Pros and cons]\n\n**CodexBar Current State:**\n- [What we have now]\n- [Gaps or limitations]\n\n**Adaptation Plan:**\n- [How we'll implement similar functionality]\n- [What we'll do differently]\n- [Why our approach is better/different]\n\n**Implementation Tasks:**\n- [ ] Task 1\n- [ ] Task 2\n- [ ] Task 3\n\n**Code Attribution:**\n```swift\n// Inspired by quotio's approach to [feature]:\n// https://github.com/nguyenphutrong/quotio/blob/main/path/to/file\n// Implemented independently using CodexBar patterns\n```\n\n---\n\n## 🎨 Pattern Examples\n\n### Example 1: Multi-Account UI Pattern\n\n**Quotio Pattern (Observed):**\n- Dropdown menu in menu bar\n- Account nickname/email display\n- Active account indicator\n- Quick switch action\n\n**CodexBar Adaptation:**\n```swift\n// Our implementation (example)\nstruct AccountSwitcherView: View {\n    @Bindable var store: UsageStore\n    \n    var body: some View {\n        Menu {\n            ForEach(store.accounts) { account in\n                Button {\n                    store.switchAccount(account)\n                } label: {\n                    HStack {\n                        Text(account.displayName)\n                        if account.isActive {\n                            Image(systemName: \"checkmark\")\n                        }\n                    }\n                }\n            }\n        } label: {\n            // Menu bar icon\n        }\n    }\n}\n\n// Inspired by quotio's account switching UI pattern\n// Implemented using SwiftUI and CodexBar's UsageStore\n```\n\n### Example 2: Session Persistence Pattern\n\n**Quotio Pattern (Observed):**\n- Automatic session restoration\n- Background refresh\n- Error recovery\n\n**CodexBar Adaptation:**\n```swift\n// Our implementation (example)\nactor SessionPersistence {\n    func saveSession(_ session: SessionInfo) async throws {\n        // Our keychain-based approach\n    }\n    \n    func restoreSession() async throws -> SessionInfo? {\n        // Our restoration logic\n    }\n}\n\n// Inspired by quotio's session persistence approach\n// Implemented using Swift concurrency and CodexBar's keychain utilities\n```\n\n---\n\n## 📋 Analysis Checklist\n\n### Initial Review\n- [ ] Clone/fetch quotio repository\n- [ ] Review README and documentation\n- [ ] Check license compatibility\n- [ ] Identify main features\n- [ ] Create feature comparison matrix\n\n### Deep Dive\n- [ ] Multi-account management\n- [ ] Session/cookie handling\n- [ ] UI/UX patterns\n- [ ] Provider architecture\n- [ ] Error handling\n- [ ] Performance optimizations\n\n### Documentation\n- [ ] Document patterns (not code)\n- [ ] Create adaptation plans\n- [ ] Identify implementation tasks\n- [ ] Prioritize features\n- [ ] Estimate effort\n\n### Implementation\n- [ ] Implement independently\n- [ ] Follow CodexBar conventions\n- [ ] Add proper attribution\n- [ ] Write tests\n- [ ] Update documentation\n\n---\n\n## 🚀 Priority Features from Quotio\n\n### High Priority\n1. **Multi-Account Management**\n   - Status: Not started\n   - Effort: Large\n   - Value: High\n   - Dependencies: Account storage, UI updates\n\n2. **Enhanced Session Management**\n   - Status: Partial (have keepalive)\n   - Effort: Medium\n   - Value: High\n   - Dependencies: None\n\n3. **Improved Error Messages**\n   - Status: Basic\n   - Effort: Small\n   - Value: Medium\n   - Dependencies: None\n\n### Medium Priority\n4. **Menu Bar Organization**\n   - Status: Basic\n   - Effort: Medium\n   - Value: Medium\n   - Dependencies: Multi-account\n\n5. **Settings Layout**\n   - Status: Functional\n   - Effort: Small\n   - Value: Low\n   - Dependencies: None\n\n### Low Priority\n6. **Additional Providers**\n   - Status: Have 5\n   - Effort: Varies\n   - Value: Medium\n   - Dependencies: Provider framework\n\n---\n\n## 📝 Notes & Observations\n\n### General Observations\n- [Add observations as you analyze]\n- [Note interesting patterns]\n- [Document questions]\n\n### Architectural Differences\n- [How quotio differs from CodexBar]\n- [Pros and cons of each approach]\n- [What we can learn]\n\n### Implementation Ideas\n- [Ideas sparked by quotio]\n- [How to adapt to CodexBar]\n- [Potential improvements]\n\n---\n\n## 🔗 Resources\n\n- **Quotio Repository:** https://github.com/nguyenphutrong/quotio\n- **Analysis Script:** `./Scripts/analyze_quotio.sh`\n- **Review Command:** `git show quotio/main:path/to/file`\n- **Diff Command:** `git diff main quotio/main -- path/to/file`\n\n---\n\n## ⚖️ Legal & Ethical Considerations\n\n### License Compliance\n- Quotio's license: [Check their LICENSE file]\n- Our approach: Learn patterns, implement independently\n- Attribution: Credit inspiration in commits and docs\n\n### Ethical Guidelines\n1. Never copy code verbatim\n2. Understand the pattern before implementing\n3. Implement using our own logic and style\n4. Credit inspiration appropriately\n5. Respect their intellectual property\n\n### Attribution Format\n```\nInspired by quotio's approach to [feature]:\nhttps://github.com/nguyenphutrong/quotio/blob/main/path/to/file\n\nImplemented independently using CodexBar patterns and conventions.\n```\n\n---\n\n**Last Updated:** [Date]  \n**Analyzed By:** [Your Name]  \n**Status:** [In Progress / Complete]\n"
  },
  {
    "path": "docs/RELEASING.md",
    "content": "---\nsummary: \"CodexBar release checklist: package, sign, notarize, appcast, and asset validation.\"\nread_when:\n  - Starting a CodexBar release\n  - Updating signing/notarization or appcast steps\n  - Validating release assets or Sparkle feed\n---\n\n# Release process (CodexBar)\n\nSwiftPM-only; package/sign/notarize manually (no Xcode project). Sparkle feed is served from GitHub Releases. Checklist below merges Trimmy’s release flow with CodexBar specifics.\n\n**Must read first:** open the master macOS release guide at `~/Projects/agent-scripts/docs/RELEASING-MAC.md` alongside this file and reconcile any differences in favor of CodexBar specifics before starting a release.\n\n## Expectations\n- When someone says “release CodexBar”, do the entire end-to-end flow: bump versions/CHANGELOG, build, sign and notarize, upload the zip to the GitHub release, generate/update the appcast with the new signature, publish the tag/release, and verify the enclosure URL responds with 200/OK and installs via Sparkle (no 404s or stale feeds).\n\n### Release automation notes (Scripts/release.sh)\n- Always forces a fresh build/notarization (no cached artifacts) before publishing.\n- Fails fast if: git tree is dirty, the top changelog section is still “Unreleased” or mismatched, the target version already exists in the appcast, or the build number is not greater than the latest appcast entry.\n- Sparkle key probe runs up front; appcast entry + signature verified automatically after generation.\n- Release notes are extracted directly from the current changelog section and passed to the GitHub release (no manual notes flag needed).\n- Sparkle appcast notes are generated as HTML from the same changelog section and embedded into the appcast entry.\n- Requires tools/env on PATH: `swiftformat`, `swiftlint`, `swift`, `sign_update`, `generate_appcast`, `gh`, `python3`, `zip`, `curl`, plus `APP_STORE_CONNECT_*` and `SPARKLE_PRIVATE_KEY_FILE`.\n\n## Prereqs\n- Xcode 26+ installed at `/Applications/Xcode.app` (for ictool/iconutil and SDKs).\n- Developer ID Application cert installed: `Developer ID Application: Peter Steinberger (Y5PE65HELJ)`.\n- ASC API creds in env: `APP_STORE_CONNECT_API_KEY_P8`, `APP_STORE_CONNECT_KEY_ID`, `APP_STORE_CONNECT_ISSUER_ID`.\n- Sparkle keys: public key already in Info.plist; private key path set via `SPARKLE_PRIVATE_KEY_FILE` when generating appcast.\n- Ensure shell has release env vars loaded (usually `source ~/.profile`) before running `Scripts/release.sh`.\n\n## Icon (glass .icon → .icns)\n```\n./Scripts/build_icon.sh Icon.icon CodexBar\n```\nUses Xcode’s `ictool` + transparent padding + iconset → Icon.icns.\n\n## Build, sign, notarize (universal: arm64 + x86_64)\n```\n./Scripts/sign-and-notarize.sh\n```\nWhat it does:\n- `swift build -c release --arch arm64` and `swift build -c release --arch x86_64`\n- Packages `CodexBar.app` with Info.plist and Icon.icns\n- Embeds Sparkle.framework, Updater, Autoupdate, XPCs\n- Codesigns **everything** with runtime + timestamp (deep) and adds rpath\n- Zips to `CodexBar-<version>.zip`\n- Submits to notarytool, waits, staples, validates\n\nGotchas fixed:\n- Sparkle needs signing for framework, Autoupdate, Updater, XPCs (Downloader/Installer) or notarization fails.\n- Use `--timestamp` and `--deep` when signing the app to avoid invalid signature errors.\n- Avoid `unzip` — it can add AppleDouble `._*` files that break the sealed signature and trigger “app is damaged”. Use Finder or `ditto -x -k CodexBar-<ver>.zip /Applications`. If Gatekeeper complains, delete the app bundle, re-extract with `ditto`, then `spctl -a -t exec` to verify.\n- Manual sanity check before uploading: `find CodexBar.app -name '._*'` should return nothing; then `spctl --assess --type execute --verbose CodexBar.app` and `codesign --verify --deep --strict --verbose CodexBar.app` should both pass on the packaged bundle.\n\n## Appcast (Sparkle)\nAfter notarization:\n```\nSPARKLE_PRIVATE_KEY_FILE=/path/to/ed25519-priv.key \\\n./Scripts/make_appcast.sh CodexBar-0.1.0.zip \\\n  https://raw.githubusercontent.com/steipete/CodexBar/main/appcast.xml\nGenerates HTML release notes from `CHANGELOG.md` (via `Scripts/changelog-to-html.sh`) and embeds them into the appcast entry.\n```\nUploads not handled automatically—commit/publish appcast + zip to the feed location (GitHub Releases/raw URL).\n\n## Tag & release\n```\ngit tag v<version>\n./Scripts/make_appcast.sh ...\n# upload zip + appcast to Releases\n# then create GitHub release (gh release create v<version> ...)\n```\n\n## Homebrew (Cask)\nCodexBar ships a Homebrew **Cask** in `../homebrew-tap`. When installed via Homebrew, CodexBar disables Sparkle and the app\nmust be updated via `brew`.\n\nAfter publishing the GitHub release, update the tap cask + Linux CLI formula (see `docs/releasing-homebrew.md`).\n\n## Checklist (quick)\n- [ ] Read both this file and `~/Projects/agent-scripts/docs/RELEASING-MAC.md`; resolve any conflicts toward CodexBar’s specifics.\n- [ ] Update versions (scripts/Info.plist, CHANGELOG, About text) — changelog top section must be finalized; release script pulls notes from it automatically.\n- [ ] `swiftformat`, `swiftlint`, `swift test` (zero warnings/errors)\n- [ ] `./Scripts/build_icon.sh` if icon changed\n- [ ] `./Scripts/sign-and-notarize.sh`\n- [ ] Generate Sparkle appcast with private key\n  - Sparkle ed25519 private key path: `/Users/steipete/Library/CloudStorage/Dropbox/Backup/Sparkle/sparkle-private-key-KEEP-SECURE.txt` (primary) and `/Users/steipete/Library/CloudStorage/Dropbox/Backup/Sparkle-VibeTunnel/sparkle-private-key-KEEP-SECURE.txt` (older backup)\n  - Upload the dSYM archive alongside the app zip on the GitHub release; the release script now automates this and will fail if it’s missing.\n  - After publishing the release, run `Scripts/check-release-assets.sh <tag>` to confirm both the app zip and dSYM zip are present on GitHub.\n  - Generate the appcast + HTML release notes: `./Scripts/make_appcast.sh CodexBar-<ver>.zip https://raw.githubusercontent.com/steipete/CodexBar/main/appcast.xml`\n  - Beta channel: prefix the command with `SPARKLE_CHANNEL=beta` to tag the entry.\n  - Verify the enclosure signature + size: `SPARKLE_PRIVATE_KEY_FILE=... ./Scripts/verify_appcast.sh <ver>`\n- [ ] Upload zip + appcast to feed; publish tag + GitHub release so Sparkle URL is live (avoid 404)\n- [ ] Homebrew tap: update `../homebrew-tap/Casks/codexbar.rb` (url + sha256) and `../homebrew-tap/Formula/codexbar.rb` (Linux CLI tarball urls + sha256), then verify:\n  - `brew uninstall --cask codexbar || true`\n  - `brew untap steipete/tap || true; brew tap steipete/tap`\n  - `brew install --cask steipete/tap/codexbar && open -a CodexBar`\n- [ ] Version continuity: confirm the new version is the immediate next patch/minor (no gaps) and CHANGELOG has no skipped numbers (e.g., after 0.2.0 use 0.2.1, not 0.2.2)\n- [ ] Changelog sanity: single top-level title, no duplicate version sections, versions strictly descending with no repeats\n- [ ] Release pages: title format `CodexBar <version>`, notes as Markdown list (no stray blank lines)\n- [ ] Changelog/release notes are user-facing: avoid internal-only bullets (build numbers, script bumps) and keep entries concise\n- [ ] Download uploaded `CodexBar-<ver>.zip`, unzip via `ditto`, run, and verify signature (`spctl -a -t exec -vv CodexBar.app` + `stapler validate`)\n- [ ] Confirm `appcast.xml` points to the new zip/version and renders the HTML release notes (not escaped tags)\n- [ ] Verify on GitHub Releases: assets present (zip, appcast), release notes match changelog, version/tag correct\n- [ ] Open the appcast URL in browser to confirm the new entry is visible and enclosure URL is reachable\n- [ ] Manually visit the enclosure URL (curl -I) to ensure 200/OK (no 404) after publishing assets/release\n- [ ] Ensure `sparkle:edSignature` is present for the enclosure in appcast (generated by `generate_appcast` with the ed25519 key)\n- [ ] When creating the GitHub release, paste the CHANGELOG entry as Markdown list (one `-` per line, blank line between sections); visually confirm bullets render correctly after publishing\n- [ ] Keep a previous signed build in `/Applications/CodexBar.app` to test Sparkle delta/full update to the new release\n- [ ] Manual Gatekeeper sanity: after packaging, `find CodexBar.app -name '._*'` is empty, `spctl --assess --type execute --verbose CodexBar.app` and `codesign --verify --deep --strict --verbose CodexBar.app` succeed\n- [ ] For Sparkle verification: if replacing `/Applications/CodexBar.app`, quit first, replace, relaunch, and test update\n- **Definition of “done” for a release:** all of the above are complete, the appcast/enclosure link resolves, Homebrew cask\n  installs, and a previous public build can update to the new one via Sparkle. Anything short of that is not a finished release.\n\n## Troubleshooting\n- **White plate icon**: regenerate icns via `build_icon.sh` (ictool) to ensure transparent padding.\n- **Notarization invalid**: verify deep+timestamp signing, especially Sparkle’s Autoupdate/Updater and XPCs; rerun package + sign-and-notarize.\n- **App won’t launch**: ensure Sparkle.framework is embedded under `Contents/Frameworks` and rpath added; codesign deep.\n- **App “damaged” dialog after unzip**: re-extract with `ditto -x -k`, removing any `._*` files, then re-verify with `spctl`.\n- **Update download fails (404)**: ensure the release asset referenced in appcast exists and is published in the corresponding GitHub release; verify with `curl -I <enclosure-url>`.\n"
  },
  {
    "path": "docs/TODO.md",
    "content": "---\nsummary: \"Pending cleanup items for CodexBar (e.g., retire Claude Opus fallback).\"\nread_when:\n  - Grooming backlog or planning maintenance\n  - Removing legacy Claude Opus handling\n---\n\n## TODO\n\n- December 2025: remove the Claude Opus fallback logic (Claude Code) once users have fully migrated; clean up any UI labels and parsers that reference Opus as a tertiary limit.\n"
  },
  {
    "path": "docs/UPSTREAM_STRATEGY.md",
    "content": "---\nsummary: \"Upstream strategy for forks: remotes, cherry-picks, and contribution policy.\"\nread_when:\n  - Managing fork/upstream workflow\n  - Planning contributions or syncs\n---\n\n# Multi-Upstream Fork Management Strategy\n\n**Fork:** topoffunnel/CodexBar  \n**Upstream 1:** steipete/CodexBar (original)  \n**Upstream 2:** nguyenphutrong/quotio (inspiration source)\n\n---\n\n## 🎯 Core Principles\n\n### Fork Independence\n- **Your fork is the primary development target**\n- Upstream contributions are optional and selective\n- You retain full credit for your innovations\n- Fork-specific features stay in the fork\n\n### Selective Contribution\n- Only contribute universally beneficial changes upstream\n- Keep attribution-sensitive improvements in fork\n- Submit small, focused PRs to increase merge likelihood\n- Don't contribute fork branding or identity\n\n### Best-of-Both-Worlds\n- Monitor both upstreams for valuable changes\n- Cherry-pick features that enhance your fork\n- Adapt patterns without copying code\n- Credit sources appropriately\n\n---\n\n## 🌳 Git Repository Structure\n\n### Remote Configuration\n\n```bash\n# Your fork (origin)\ngit remote add origin git@github.com:topoffunnel/CodexBar.git\n\n# Original upstream (steipete)\ngit remote add upstream git@github.com:steipete/CodexBar.git\n\n# Quotio inspiration source\ngit remote add quotio git@github.com:nguyenphutrong/quotio.git\n\n# Verify remotes\ngit remote -v\n```\n\n### Branch Strategy\n\n```\nmain (your fork's stable branch)\n├── feature/* (fork-specific features)\n├── upstream-sync/* (tracking upstream changes)\n├── quotio-inspired/* (features inspired by quotio)\n└── upstream-pr/* (branches for upstream PRs)\n```\n\n**Branch Types:**\n- `main` - Your fork's stable release branch\n- `feature/*` - Fork-specific development\n- `upstream-sync/*` - Temporary branches for reviewing upstream changes\n- `quotio-inspired/*` - Features adapted from quotio patterns\n- `upstream-pr/*` - Clean branches for upstream contributions\n\n---\n\n## 🔄 Workflow 1: Monitoring Upstream Changes\n\n### Daily/Weekly Sync Check\n\n```bash\n#!/bin/bash\n# Scripts/check_upstreams.sh\n\necho \"==> Fetching upstream changes...\"\ngit fetch upstream\ngit fetch quotio\n\necho \"\"\necho \"==> Upstream (steipete) changes:\"\ngit log --oneline main..upstream/main --no-merges | head -20\n\necho \"\"\necho \"==> Quotio changes:\"\ngit log --oneline --all --remotes=quotio/main --since=\"1 week ago\" | head -20\n\necho \"\"\necho \"==> Files changed in upstream:\"\ngit diff --stat main..upstream/main\n\necho \"\"\necho \"==> Files changed in quotio (recent):\"\ngit diff --stat quotio/main~10..quotio/main\n```\n\n### Automated Monitoring (GitHub Actions)\n\nCreate `.github/workflows/upstream-monitor.yml`:\n\n```yaml\nname: Monitor Upstreams\n\non:\n  schedule:\n    - cron: '0 9 * * 1,4'  # Monday and Thursday at 9 AM\n  workflow_dispatch:\n\njobs:\n  check-upstream:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n      \n      - name: Add upstream remotes\n        run: |\n          git remote add upstream https://github.com/steipete/CodexBar.git\n          git remote add quotio https://github.com/nguyenphutrong/quotio.git\n          git fetch upstream\n          git fetch quotio\n      \n      - name: Check for new commits\n        id: check\n        run: |\n          UPSTREAM_NEW=$(git log --oneline main..upstream/main --no-merges | wc -l)\n          QUOTIO_NEW=$(git log --oneline --all --remotes=quotio/main --since=\"1 week ago\" | wc -l)\n          \n          echo \"upstream_commits=$UPSTREAM_NEW\" >> $GITHUB_OUTPUT\n          echo \"quotio_commits=$QUOTIO_NEW\" >> $GITHUB_OUTPUT\n      \n      - name: Create issue if changes detected\n        if: steps.check.outputs.upstream_commits > 0 || steps.check.outputs.quotio_commits > 0\n        uses: actions/github-script@v7\n        with:\n          script: |\n            const upstreamCommits = '${{ steps.check.outputs.upstream_commits }}';\n            const quotioCommits = '${{ steps.check.outputs.quotio_commits }}';\n            \n            const body = `## Upstream Changes Detected\n            \n            **steipete/CodexBar:** ${upstreamCommits} new commits\n            **quotio:** ${quotioCommits} new commits (last week)\n            \n            Review changes:\n            - [steipete commits](https://github.com/steipete/CodexBar/compare/main...upstream/main)\n            - [quotio commits](https://github.com/nguyenphutrong/quotio/commits/main)\n            \n            Run \\`./Scripts/review_upstream.sh\\` to analyze changes.`;\n            \n            github.rest.issues.create({\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n              title: 'Upstream Changes Available',\n              body: body,\n              labels: ['upstream-sync']\n            });\n```\n\n---\n\n## 🔍 Workflow 2: Reviewing & Incorporating Changes\n\n### Step 1: Review Upstream Changes\n\n```bash\n#!/bin/bash\n# Scripts/review_upstream.sh\n\nUPSTREAM=${1:-upstream}  # 'upstream' or 'quotio'\n\necho \"==> Creating review branch for $UPSTREAM...\"\ngit checkout main\ngit checkout -b upstream-sync/$UPSTREAM-$(date +%Y%m%d)\n\necho \"==> Fetching latest...\"\ngit fetch $UPSTREAM\n\necho \"==> Showing commits to review:\"\ngit log --oneline --graph main..$UPSTREAM/main | head -30\n\necho \"\"\necho \"==> Detailed diff:\"\ngit diff main..$UPSTREAM/main --stat\n\necho \"\"\necho \"Next steps:\"\necho \"1. Review commits: git log -p main..$UPSTREAM/main\"\necho \"2. Cherry-pick specific commits: git cherry-pick <commit-hash>\"\necho \"3. Or merge all: git merge $UPSTREAM/main\"\necho \"4. Test thoroughly\"\necho \"5. Merge to main: git checkout main && git merge upstream-sync/$UPSTREAM-$(date +%Y%m%d)\"\n```\n\n### Step 2: Selective Cherry-Picking\n\n```bash\n# Review individual commits\ngit log -p main..upstream/main\n\n# Cherry-pick specific valuable commits\ngit cherry-pick <commit-hash>\n\n# If conflicts, resolve and continue\ngit cherry-pick --continue\n\n# Or abort if not suitable\ngit cherry-pick --abort\n```\n\n### Step 3: Quotio Pattern Adaptation\n\n```bash\n# Create inspiration branch\ngit checkout -b quotio-inspired/feature-name\n\n# View quotio implementation (read-only)\ngit show quotio/main:path/to/file.swift\n\n# Implement similar pattern in your codebase\n# (write your own code, don't copy)\n\n# Commit with attribution\ngit commit -m \"feat: implement feature inspired by quotio\n\nInspired by quotio's approach to [feature]:\nhttps://github.com/nguyenphutrong/quotio/commit/abc123\n\nImplemented independently with CodexBar-specific patterns.\"\n```\n\n---\n\n## 📤 Workflow 3: Contributing to Upstream\n\n### Identifying Upstream-Suitable Changes\n\n**✅ Good for Upstream:**\n- Bug fixes that affect all users\n- Performance improvements\n- Provider enhancements (non-fork-specific)\n- Documentation improvements\n- Test coverage additions\n- Dependency updates\n\n**❌ Keep in Fork:**\n- Fork branding/attribution\n- Multi-account management (major architectural change)\n- Fork-specific UI customizations\n- Experimental features\n- topoffunnel.com-specific integrations\n\n### Creating Upstream PR Branch\n\n```bash\n#!/bin/bash\n# Scripts/prepare_upstream_pr.sh\n\nFEATURE_NAME=$1\n\nif [ -z \"$FEATURE_NAME\" ]; then\n  echo \"Usage: ./Scripts/prepare_upstream_pr.sh <feature-name>\"\n  exit 1\nfi\n\necho \"==> Creating upstream PR branch...\"\ngit checkout upstream/main\ngit checkout -b upstream-pr/$FEATURE_NAME\n\necho \"==> Branch created: upstream-pr/$FEATURE_NAME\"\necho \"\"\necho \"Next steps:\"\necho \"1. Cherry-pick your commits (without fork branding)\"\necho \"2. Remove any fork-specific code\"\necho \"3. Ensure tests pass\"\necho \"4. Push: git push origin upstream-pr/$FEATURE_NAME\"\necho \"5. Create PR to steipete/CodexBar from GitHub UI\"\n```\n\n### Cleaning Commits for Upstream\n\n```bash\n# Start from upstream's main\ngit checkout upstream/main\ngit checkout -b upstream-pr/fix-cursor-bonus\n\n# Cherry-pick your fix (without fork branding)\ngit cherry-pick <your-commit-hash>\n\n# If commit includes fork branding, amend it\ngit commit --amend\n\n# Remove fork-specific changes\ngit reset HEAD~1\n# Manually stage only upstream-suitable changes\ngit add <files>\ngit commit -m \"fix: correct Cursor bonus credits calculation\n\nFixes issue where bonus credits were incorrectly calculated.\n\nTested with multiple account types.\"\n\n# Push to your fork\ngit push origin upstream-pr/fix-cursor-bonus\n\n# Create PR to steipete/CodexBar via GitHub UI\n```\n\n---\n\n## 🏷️ Commit Message Strategy\n\n### Fork Commits (Keep Everything)\n\n```\nfeat: add multi-account management for Augment\n\nImplements account switching UI and storage.\nFork-specific feature for topoffunnel.com users.\n\nCo-authored-by: Brandon Charleson <brandon@topoffunnel.com>\n```\n\n### Upstream-Bound Commits (Generic)\n\n```\nfix: correct Cursor bonus credits calculation\n\nThe bonus credits were being added instead of subtracted\nfrom the total usage calculation.\n\nTested with Pro and Team accounts.\n```\n\n### Quotio-Inspired Commits (Attribution)\n\n```\nfeat: implement session persistence inspired by quotio\n\nAdds automatic session restoration on app restart.\n\nInspired by quotio's approach:\nhttps://github.com/nguyenphutrong/quotio/blob/main/...\n\nImplemented independently using CodexBar patterns.\n```\n\n---\n\n## 📋 Decision Matrix: What Goes Where?\n\n| Change Type | Fork | Upstream | Notes |\n|------------|------|----------|-------|\n| Bug fix (universal) | ✅ | ✅ | Submit to upstream |\n| Bug fix (fork-specific) | ✅ | ❌ | Keep in fork |\n| Performance improvement | ✅ | ✅ | Submit to upstream |\n| New provider support | ✅ | ✅ | Submit to upstream |\n| Provider enhancement | ✅ | Maybe | Depends on scope |\n| UI improvement (generic) | ✅ | ✅ | Submit to upstream |\n| UI improvement (fork brand) | ✅ | ❌ | Keep in fork |\n| Multi-account feature | ✅ | ❌ | Too large for upstream |\n| Documentation | ✅ | ✅ | Submit to upstream |\n| Tests | ✅ | ✅ | Submit to upstream |\n| Fork branding | ✅ | ❌ | Never upstream |\n| Experimental feature | ✅ | ❌ | Prove it first |\n\n---\n\n## 🔐 Protecting Your Attribution\n\n### Separate Commits Strategy\n\n```bash\n# Make changes in feature branch\ngit checkout -b feature/my-improvement\n\n# Commit 1: Core improvement (upstream-suitable)\ngit add Sources/CodexBarCore/...\ngit commit -m \"feat: improve cookie handling\"\n\n# Commit 2: Fork-specific enhancements\ngit add Sources/CodexBar/About.swift\ngit commit -m \"feat: add fork attribution for improvement\"\n\n# Merge to main (both commits)\ngit checkout main\ngit merge feature/my-improvement\n\n# For upstream PR: cherry-pick only commit 1\ngit checkout upstream/main\ngit checkout -b upstream-pr/cookie-handling\ngit cherry-pick <commit-1-hash>  # Only the core improvement\n```\n\n### Maintaining Fork Identity\n\nKeep these files fork-specific (never upstream):\n- `Sources/CodexBar/About.swift` (your attribution)\n- `Sources/CodexBar/PreferencesAboutPane.swift` (fork sections)\n- `README.md` (fork notice)\n- `docs/FORK_*.md` (fork documentation)\n- `FORK_STATUS.md`\n\n---\n\n## 🤖 Automation Scripts\n\nAll automation scripts are located in `Scripts/`:\n\n- **check_upstreams.sh** - Check for new commits in both upstreams\n- **review_upstream.sh** - Create review branch for upstream changes\n- **prepare_upstream_pr.sh** - Prepare clean branch for upstream PR\n- **analyze_quotio.sh** - Analyze quotio for patterns and features\n\nGitHub Actions workflow: `.github/workflows/upstream-monitor.yml`\n\n---\n\n## 📖 Practical Examples\n\n### Example 1: Weekly Upstream Check\n\n```bash\n# Monday morning routine\n./Scripts/check_upstreams.sh\n\n# If changes found, review them\n./Scripts/review_upstream.sh upstream\n\n# Cherry-pick valuable commits\ngit cherry-pick abc123\ngit cherry-pick def456\n\n# Test\n./Scripts/compile_and_run.sh\n\n# Merge to main\ngit checkout main\ngit merge upstream-sync/upstream-20260104\n```\n\n### Example 2: Contributing Bug Fix Upstream\n\n```bash\n# You fixed a bug in your fork\ngit log --oneline -5\n# abc123 fix: correct Cursor bonus credits\n# def456 feat: add fork attribution\n\n# Prepare upstream PR (only the fix, not attribution)\n./Scripts/prepare_upstream_pr.sh fix-cursor-bonus\n\n# Cherry-pick only the fix\ngit cherry-pick abc123\n\n# Review - ensure no fork branding\ngit diff upstream/main\n\n# Push and create PR\ngit push origin upstream-pr/fix-cursor-bonus\n# Then create PR on GitHub to steipete/CodexBar\n```\n\n### Example 3: Learning from Quotio\n\n```bash\n# Analyze quotio\n./Scripts/analyze_quotio.sh\n\n# Review their multi-account implementation\ngit show quotio/main:path/to/AccountManager.swift\n\n# Document patterns in docs/QUOTIO_ANALYSIS.md\n# Then implement independently in your fork\n\n# Commit with attribution\ngit commit -m \"feat: implement multi-account management\n\nInspired by quotio's account switching pattern:\nhttps://github.com/nguyenphutrong/quotio/...\n\nImplemented independently using CodexBar's architecture.\"\n```\n\n---\n\n## 🎓 Best Practices\n\n### For Fork Development\n1. **Commit often** - Small, focused commits\n2. **Separate concerns** - Fork branding in separate commits\n3. **Test thoroughly** - Every change\n4. **Document decisions** - Why you chose this approach\n5. **Credit sources** - When inspired by others\n\n### For Upstream Contributions\n1. **Start small** - Bug fixes before features\n2. **One thing per PR** - Focused changes\n3. **Follow their style** - Match upstream conventions\n4. **Include tests** - Prove it works\n5. **Be patient** - Maintainers are busy\n\n### For Multi-Upstream Sync\n1. **Check weekly** - Stay current\n2. **Review carefully** - Understand before merging\n3. **Test everything** - Upstream changes may break your fork\n4. **Document conflicts** - How you resolved them\n5. **Keep attribution** - Credit all sources\n\n---\n\n## 🔧 Troubleshooting\n\n### Merge Conflicts\n\n```bash\n# During upstream merge\ngit merge upstream/main\n# CONFLICT in Sources/CodexBar/About.swift\n\n# Keep your fork version for branding files\ngit checkout --ours Sources/CodexBar/About.swift\ngit add Sources/CodexBar/About.swift\n\n# Merge other files manually\n# Then continue\ngit commit\n```\n\n### Accidentally Pushed Fork Branding to Upstream PR\n\n```bash\n# Oops! Pushed fork branding to upstream PR branch\ngit checkout upstream-pr/my-feature\n\n# Reset to before the bad commit\ngit reset --hard HEAD~1\n\n# Re-apply changes without branding\n# ... make changes ...\ngit commit -m \"fix: proper commit\"\n\n# Force push (only safe on PR branches)\ngit push origin upstream-pr/my-feature --force\n```\n\n### Lost Track of Upstream Changes\n\n```bash\n# See what you've merged from upstream\ngit log --oneline --graph --all --grep=\"upstream\"\n\n# See what's still pending\ngit log --oneline main..upstream/main\n\n# Create a tracking branch\ngit checkout -b upstream-tracking upstream/main\ngit log --oneline upstream-tracking..main\n```\n\n---\n\n## 📊 Success Metrics\n\n### Fork Health\n- ✅ Builds without errors\n- ✅ All tests passing\n- ✅ No regressions from upstream merges\n- ✅ Fork-specific features working\n- ✅ Documentation up to date\n\n### Upstream Relationship\n- ✅ PRs are small and focused\n- ✅ PRs get merged (or constructive feedback)\n- ✅ Maintain good relationship with maintainer\n- ✅ Credit given appropriately\n- ✅ No fork branding in upstream PRs\n\n### Multi-Source Learning\n- ✅ Regular upstream monitoring\n- ✅ Quotio patterns documented\n- ✅ Independent implementations\n- ✅ Proper attribution\n- ✅ Best-of-both-worlds achieved\n\n---\n\n## 🗓️ Recommended Schedule\n\n### Weekly\n- Monday: Check upstreams (`./Scripts/check_upstreams.sh`)\n- Thursday: Review quotio (`./Scripts/analyze_quotio.sh`)\n\n### Monthly\n- Review upstream PRs you submitted\n- Update QUOTIO_ANALYSIS.md with new findings\n- Sync with upstream main\n- Update fork documentation\n\n### Quarterly\n- Major feature planning\n- Upstream contribution strategy review\n- Fork roadmap update\n- Community engagement\n\n---\n\n## 📞 Getting Help\n\n### Upstream Issues\n- Check their issue tracker first\n- Ask in discussions if available\n- Be respectful and patient\n- Provide minimal reproduction\n\n### Fork Issues\n- Document in your fork's issues\n- Reference upstream if relevant\n- Track in FORK_STATUS.md\n- Update roadmap as needed\n\n### Quotio Questions\n- Review their documentation\n- Check their issue tracker\n- Don't ask them to help with your fork\n- Credit them when you adapt patterns\n\n---\n\n**Remember:** Your fork is independent. Upstream contributions are optional. Learn from others, but implement independently. Credit sources appropriately.\n"
  },
  {
    "path": "docs/alibaba-coding-plan.md",
    "content": "---\nsummary: \"Alibaba Coding Plan provider data sources: browser-session baseline, secondary API mode, and honest quota fallback behavior.\"\nread_when:\n  - Debugging Alibaba Coding Plan API key handling or quota parsing\n  - Updating Alibaba Coding Plan endpoints or region behavior\n  - Adjusting Alibaba Coding Plan provider UI/menu behavior\n---\n\n# Alibaba Coding Plan provider\n\nAlibaba Coding Plan supports both browser-session and API-key paths, but the supported baseline is browser-session fetching from the Model Studio/Bailian console. API mode remains secondary and may still be limited by account/region behavior.\n\n## Cookie sources (web mode)\n1) Automatic browser import (Model Studio/Bailian cookies).\n2) Manual cookie header from Settings.\n3) Environment variable `ALIBABA_CODING_PLAN_COOKIE`.\n\nWhen the RPC endpoint returns `ConsoleNeedLogin`, CodexBar treats that as a console-session requirement. In API mode it is surfaced as an explicit API-path limitation; in `auto` mode fallback remains observable through the fetch-attempt chain.\n\n## Token sources (fallback order)\n1) Config token (`~/.codexbar/config.json` -> `providers[].apiKey` for provider `alibaba`).\n2) Environment variable `ALIBABA_CODING_PLAN_API_KEY`.\n\n## Region + endpoint behavior\n- International host: `https://modelstudio.console.alibabacloud.com`\n- China mainland host: `https://bailian.console.aliyun.com`\n- Quota request path:\n  - `POST /data/api.json?action=zeldaEasy.broadscope-bailian.codingPlan.queryCodingPlanInstanceInfoV2&product=broadscope-bailian&api=queryCodingPlanInstanceInfoV2`\n- Region is selected in Preferences -> Providers -> Alibaba Coding Plan -> Gateway region.\n- Auto fallback behavior:\n  - If International fails with credential/host-style API errors, CodexBar retries China mainland once.\n\n### CN API-key limitation (known)\n- In some China mainland accounts/environments, the current Alibaba `/data/api.json` coding-plan endpoint can still return console-login-required responses (`ConsoleNeedLogin`) even when an API key is configured.\n- In that case, API-key mode may not be functionally available for that account/endpoint, and web session mode is required.\n- CodexBar now surfaces this as an API error in API mode (instead of a cookie-login-required message) so the limitation is explicit.\n\n## Overrides\n- Override host base: `ALIBABA_CODING_PLAN_HOST`\n  - Example: `ALIBABA_CODING_PLAN_HOST=modelstudio.console.alibabacloud.com`\n- Override full quota URL: `ALIBABA_CODING_PLAN_QUOTA_URL`\n  - Example: `ALIBABA_CODING_PLAN_QUOTA_URL=https://example.com/data/api.json?action=...`\n\n## Request headers\n- `Authorization: Bearer <api_key>`\n- `x-api-key: <api_key>`\n- `X-DashScope-API-Key: <api_key>`\n- `Content-Type: application/json`\n- `Accept: application/json`\n\n## Parsing + mapping\n- Plan name (best effort):\n  - `codingPlanInstanceInfos[].planName` / `instanceName` / `packageName`\n- Quota windows (from `codingPlanQuotaInfo`):\n  - `per5HourUsedQuota` + `per5HourTotalQuota` + `per5HourQuotaNextRefreshTime` -> primary (5-hour)\n  - `perWeekUsedQuota` + `perWeekTotalQuota` + `perWeekQuotaNextRefreshTime` -> secondary (weekly)\n  - `perBillMonthUsedQuota` + `perBillMonthTotalQuota` + `perBillMonthQuotaNextRefreshTime` -> tertiary (monthly)\n- Each window maps to `usedPercent = used / total * 100` (bounded to valid range).\n- If the payload proves the plan is active but does not expose defensible quota counters, CodexBar preserves the visible plan state without manufacturing a normal quantitative quota window.\n- If neither real counters nor a defensible active-plan fallback signal exist, parsing fails explicitly instead of degrading to fake `0%` usage.\n\n## Dashboard links\n- International console: `https://modelstudio.console.alibabacloud.com/ap-southeast-1/?tab=globalset#/efm/coding_plan`\n- China mainland console: `https://bailian.console.aliyun.com/cn-beijing/?tab=model#/efm/coding_plan`\n\n## Key files\n- `Sources/CodexBarCore/Providers/Alibaba/AlibabaCodingPlanProviderDescriptor.swift`\n- `Sources/CodexBarCore/Providers/Alibaba/AlibabaCodingPlanUsageFetcher.swift`\n- `Sources/CodexBarCore/Providers/Alibaba/AlibabaCodingPlanUsageSnapshot.swift`\n- `Sources/CodexBar/Providers/Alibaba/AlibabaCodingPlanProviderImplementation.swift`\n"
  },
  {
    "path": "docs/amp.md",
    "content": "---\nsummary: \"Amp provider notes: settings scrape, cookie auth, and free-tier usage.\"\nread_when:\n  - Adding or modifying the Amp provider\n  - Debugging Amp cookie import or settings parsing\n  - Adjusting Amp menu labels or usage math\n---\n\n# Amp Provider\n\nThe Amp provider tracks your Amp Free usage by scraping the Amp settings page with browser cookies.\n\n## Features\n\n- **Amp Free meter**: Shows how much daily free usage remains.\n- **Time-to-full reset**: “Resets in …” indicates when free usage replenishes to full.\n- **Browser cookie auth**: No API keys needed.\n\n## Setup\n\n1. Open **Settings → Providers**\n2. Enable **Amp**\n3. Leave **Cookie source** on **Auto** (recommended)\n\n### Manual cookie import (optional)\n\n1. Open `https://ampcode.com/settings`\n2. Copy a `Cookie:` header from your browser’s Network tab\n3. Paste it into **Amp → Cookie Source → Manual**\n\n## How it works\n\n- Fetches `https://ampcode.com/settings`\n- Parses the embedded `freeTierUsage` payload\n- Computes time-to-full from the hourly replenishment rate\n\n## Troubleshooting\n\n### “No Amp session cookie found”\n\nLog in to Amp in a supported browser (Safari or Chromium-based), then refresh in CodexBar.\n\n### “Amp session cookie expired”\n\nSign out and back in at `https://ampcode.com/settings`, then refresh.\n"
  },
  {
    "path": "docs/antigravity.md",
    "content": "---\nsummary: \"Antigravity provider notes: local LSP probing, port discovery, quota parsing, and UI mapping.\"\nread_when:\n  - Adding or modifying the Antigravity provider\n  - Debugging Antigravity port detection or quota parsing\n  - Adjusting Antigravity menu labels or model mapping\n---\n\n# Antigravity provider\n\nAntigravity is a local-only provider. We talk directly to the Antigravity language server running on the same machine.\n\n## Data sources + fallback order\n\n1) **Process detection**\n   - Command: `ps -ax -o pid=,command=`.\n   - Match process name: `language_server_macos` plus Antigravity markers:\n     - `--app_data_dir antigravity` OR path contains `/antigravity/`.\n   - Extract CLI flags:\n     - `--csrf_token <token>` (required).\n     - `--extension_server_port <port>` (HTTP fallback).\n\n2) **Port discovery**\n   - Command: `lsof -nP -iTCP -sTCP:LISTEN -p <pid>`.\n   - All listening ports are probed.\n\n3) **Connect port probe (HTTPS)**\n   - `POST https://127.0.0.1:<port>/exa.language_server_pb.LanguageServerService/GetUnleashData`\n   - Headers:\n     - `X-Codeium-Csrf-Token: <token>`\n     - `Connect-Protocol-Version: 1`\n   - First 200 OK response selects the connect port.\n\n4) **Quota fetch**\n   - Primary:\n     - `POST https://127.0.0.1:<connectPort>/exa.language_server_pb.LanguageServerService/GetUserStatus`\n   - Fallback:\n     - `POST https://127.0.0.1:<connectPort>/exa.language_server_pb.LanguageServerService/GetCommandModelConfigs`\n   - If HTTPS fails, retry over HTTP on `extension_server_port`.\n\n## Request body (summary)\n- Minimal metadata payload:\n  - `ideName: antigravity`\n  - `extensionName: antigravity`\n  - `locale: en`\n  - `ideVersion: unknown`\n\n## Parsing and model mapping\n- Source fields:\n  - `userStatus.cascadeModelConfigData.clientModelConfigs[].quotaInfo.remainingFraction`\n  - `userStatus.cascadeModelConfigData.clientModelConfigs[].quotaInfo.resetTime`\n- Mapping priority:\n  1) Claude (label contains `claude` but not `thinking`)\n  2) Gemini Pro Low (label contains `pro` + `low`)\n  3) Gemini Flash (label contains `gemini` + `flash`)\n  4) Fallback: lowest remaining percent\n- `resetTime` parsing:\n  - ISO-8601 preferred; numeric epoch seconds as fallback.\n- Identity:\n  - `accountEmail` and `planName` only from `GetUserStatus`.\n\n## UI mapping\n- Provider metadata:\n  - Display: `Antigravity`\n  - Labels: `Claude` (primary), `Gemini Pro` (secondary), `Gemini Flash` (tertiary)\n- Status badge: Google Workspace incidents for the Gemini product.\n\n## Constraints\n- Internal protocol; fields may change.\n- Requires `lsof` for port detection.\n- Local HTTPS uses a self-signed cert; the probe allows insecure TLS.\n\n## Key files\n- `Sources/CodexBarCore/Providers/Antigravity/AntigravityStatusProbe.swift`\n- `Sources/CodexBar/Providers/Antigravity/AntigravityProviderImplementation.swift`\n"
  },
  {
    "path": "docs/architecture.md",
    "content": "---\nsummary: \"Architecture overview: modules, entry points, and data flow.\"\nread_when:\n  - Reviewing architecture before feature work\n  - Refactoring app structure, app lifecycle, or module boundaries\n---\n\n# Architecture overview\n\n## Modules\n- `Sources/CodexBarCore`: fetch + parse (Codex RPC, PTY runner, Claude probes, OpenAI web scraping, status polling).\n- `Sources/CodexBar`: state + UI (UsageStore, SettingsStore, StatusItemController, menus, icon rendering).\n- `Sources/CodexBarWidget`: WidgetKit extension wired to the shared snapshot.\n- `Sources/CodexBarCLI`: bundled CLI for `codexbar` usage/status output.\n- `Sources/CodexBarMacros`: SwiftSyntax macros for provider registration.\n- `Sources/CodexBarMacroSupport`: shared macro support used by app/core/CLI targets.\n- `Sources/CodexBarClaudeWatchdog`: helper process for stable Claude CLI PTY sessions.\n- `Sources/CodexBarClaudeWebProbe`: CLI helper to diagnose Claude web fetches.\n\n## Entry points\n- `CodexBarApp`: SwiftUI keepalive + Settings scene.\n- `AppDelegate`: wires status controller, Sparkle updater, notifications.\n\n## Data flow\n- Background refresh → `UsageFetcher`/provider probes → `UsageStore` → menu/icon/widgets.\n- Settings toggles feed `SettingsStore` → `UsageStore` refresh cadence + feature flags.\n\n## Concurrency & platform\n- Swift 6 strict concurrency enabled; prefer Sendable state and explicit MainActor hops.\n- macOS 14+ targeting; avoid deprecated APIs when refactoring.\n\nSee also: `docs/providers.md`, `docs/refresh-loop.md`, `docs/ui.md`.\n"
  },
  {
    "path": "docs/augment.md",
    "content": "---\nsummary: \"Augment provider notes: cookie auth, keepalive, and credits parsing.\"\nread_when:\n  - Debugging Augment cookie import or keepalive\n  - Updating Augment usage or credits parsing\n  - Adjusting Augment menu labels or settings\n---\n\n# Augment Provider\n\nThe Augment provider tracks your Augment Code usage and credits through browser cookie-based authentication.\n\n## Features\n\n- **Credits Tracking**: Monitor your remaining credits and monthly limits\n- **Usage Monitoring**: Track credits consumed in the current billing cycle\n- **Plan Information**: Display your current subscription plan\n- **CLI Integration**: Uses `auggie account status` when the Auggie CLI is installed (falls back to web)\n- **Automatic Session Keepalive**: Prevents cookie expiration with proactive refresh\n- **Multi-Browser Support**: Chrome, Chrome Beta, Chrome Canary, Arc, Safari\n\n## Setup\n\n### 1. Enable the Provider\n\n1. Open **Settings → Providers**\n2. Enable **Augment**\n3. The app will automatically import cookies from your browser\n\n### 2. Cookie Source Options\n\n**Automatic (Recommended)**\n- Automatically imports cookies from your browser\n- Supports Chrome, Chrome Beta, Chrome Canary, Arc, and Safari\n- Browser priority: Chrome Beta → Chrome → Chrome Canary → Arc → Safari\n\n**Manual**\n- Paste a cookie header from your browser's developer tools\n- Useful for troubleshooting or custom browser configurations\n\n**Off**\n- Disables Augment provider entirely\n\n### 3. Verify Connection\n\n1. Check the menu bar for the Augment icon\n2. Click the icon to see your current usage\n3. If you see \"Log in to Augment\", visit [app.augmentcode.com](https://app.augmentcode.com) and sign in\n\n## How It Works\n\n### Cookie Import\n\nThe provider searches for Augment session cookies in this order:\n\n1. **Chrome Beta** (if installed)\n2. **Chrome** (if installed)\n3. **Chrome Canary** (if installed)\n4. **Arc** (if installed)\n5. **Safari** (if installed)\n\nRecognized cookie names:\n- `_session` (legacy)\n- `auth0`, `auth0.is.authenticated`, `a0.spajs.txs` (Auth0)\n- `__Secure-next-auth.session-token`, `next-auth.session-token` (NextAuth)\n- `__Host-authjs.csrf-token`, `authjs.session-token` (AuthJS)\n- `session`, `web_rpc_proxy_session` (Augment-specific)\n\nCached cookies:\n- Keychain cache `com.steipete.codexbar.cache` (account `cookie.augment`, source + timestamp). Reused before re-importing\n  from browsers.\n\n### Auggie CLI Integration\n\nIf the `auggie` CLI is installed, CodexBar will prefer `auggie account status` for usage data and avoid browser prompts.\nWhen the CLI is unavailable or not authenticated, CodexBar falls back to browser cookies.\n\n### Automatic Session Keepalive\n\nThe provider includes an automatic session keepalive system:\n\n- **Check Interval**: Every 5 minutes\n- **Refresh Buffer**: Refreshes 5 minutes before cookie expiration\n- **Rate Limiting**: Minimum 2 minutes between refresh attempts\n- **Session Cookies**: Refreshed every 30 minutes (no expiration date)\n\nThis ensures your session stays active without manual intervention.\n\n### API Endpoints\n\nThe provider fetches data from:\n- **Credits**: `https://app.augmentcode.com/api/credits`\n- **Subscription**: `https://app.augmentcode.com/api/subscription`\n\n## Troubleshooting\n\n### \"No session cookie found\"\n\n**Cause**: You're not logged into Augment in any supported browser.\n\n**Solution**:\n1. Open [app.augmentcode.com](https://app.augmentcode.com) in Chrome, Chrome Beta, or Arc\n2. Sign in to your Augment account\n3. Return to CodexBar and click \"Refresh\" in the menu\n\n### \"Session has expired\"\n\n**Cause**: Your browser session expired and automatic refresh failed.\n\n**Solution**:\n1. Visit [app.augmentcode.com](https://app.augmentcode.com)\n2. Log out and log back in\n3. Return to CodexBar - it will automatically import fresh cookies\n4. If needed, use the menu action **Refresh Session** to force a re-import\n\n### Cookies not importing from Chrome Beta\n\n**Cause**: Chrome Beta might not be in the browser search order.\n\n**Solution**: This fork includes Chrome Beta support by default. If issues persist:\n1. Check that Chrome Beta is installed at `/Applications/Google Chrome Beta.app`\n2. Verify you're logged into Augment in Chrome Beta\n3. Try switching to regular Chrome temporarily\n\n### Manual Cookie Import\n\nIf automatic import fails, you can manually paste cookies:\n\n1. Open Chrome DevTools (⌘⌥I)\n2. Go to **Application → Cookies → https://app.augmentcode.com**\n3. Copy all cookie values\n4. Format as: `cookie1=value1; cookie2=value2; ...`\n5. In CodexBar: **Settings → Providers → Augment → Cookie Source → Manual**\n6. Paste the cookie header\n\n## Debug Mode\n\nTo see detailed cookie import logs:\n\n1. Open **Settings → Debug**\n2. Find **Augment** in the provider list\n3. Click **Show Debug Info**\n\nThis displays:\n- Cookie import attempts and results\n- API request/response details\n- Session keepalive activity\n- Error messages with timestamps\n\n## Privacy & Security\n\n- Cookies are stored securely in macOS Keychain\n- Only cookies for `*.augmentcode.com` domains are imported\n- Cookies are filtered by domain before sending to API endpoints\n- No cookies are sent to third-party services\n- Session keepalive only runs when Augment is enabled\n\n## Technical Details\n\n### Cookie Domain Filtering\n\nThe provider implements RFC 6265 cookie semantics:\n\n- ✅ Exact match: `app.augmentcode.com` → `app.augmentcode.com`\n- ✅ Parent domain: `augmentcode.com` → `app.augmentcode.com`\n- ✅ Wildcard: `.augmentcode.com` → `app.augmentcode.com`\n- ❌ Different subdomain: `auth.augmentcode.com` ❌→ `app.augmentcode.com`\n\nThis prevents cookies from other subdomains being sent to the API.\n\n### Session Refresh Mechanism\n\n1. Keepalive checks cookie expiration every 5 minutes\n2. If expiration is within 5 minutes, triggers refresh\n3. Pings `/api/auth/session` to trigger cookie update\n4. Waits 1 second for browser to update cookies\n5. Re-imports fresh cookies from browser\n6. Logs success/failure for debugging\n\n## Related Documentation\n\n- [Provider Authoring Guide](provider.md) - How to create new providers\n- [Development Guide](DEVELOPMENT.md) - Build and test instructions\n"
  },
  {
    "path": "docs/claude-comparison-since-0.18.0beta2.md",
    "content": "# Claude Fetch Comparison (`7b79b2d` vs `HEAD`)\n\nThis document compares Claude data fetching behavior between:\n- Baseline commit: `7b79b2d080c6b00f6c8f52f89ac115f33a7ca8b0`\n- Current `HEAD`: `37841489f849567a598d2a8ba601eb6f1228644e`\n\nFocus areas:\n- OAuth\n- Web\n- CLI\n- Keychain permission prompts, cooldowns, and re-prompt behavior\n- OAuth token fetch/use/refresh behavior\n\n## High-level Changes\n\n- OAuth moved from \"load token and fail if expired\" to \"auto-refresh when expired\".\n- Claude keychain reads now use stricter non-interactive probes (`LAContext.interactionNotAllowed`) before any promptable path.\n- New Claude keychain prompt cooldown gate: 6-hour suppression after denial.\n- New OAuth refresh failure gate:\n  - `invalid_grant` => terminal block until auth fingerprint changes.\n  - repeated `400/401` (non-`invalid_grant`) => transient exponential backoff (up to 6h).\n- Auto-mode availability checks were hardened to avoid interactive prompts where possible.\n\n## OAuth Flow\n\n### OAuth (Before: `7b79b2d`)\n\n```mermaid\nflowchart TD\n    A[\"Claude OAuth fetch starts\"] --> B[\"load(environment)\"]\n    B --> C{\"token found?\"}\n    C -- \"no\" --> K[\"OAuth not available / fail\"]\n    C -- \"yes\" --> D{\"token expired?\"}\n    D -- \"yes\" --> E[\"Fail: token expired (run claude)\"]\n    D -- \"no\" --> F{\"has user:profile scope?\"}\n    F -- \"no\" --> G[\"Fail: missing scope\"]\n    F -- \"yes\" --> H[\"GET /api/oauth/usage with Bearer access token\"]\n    H --> I{\"HTTP success?\"}\n    I -- \"yes\" --> J[\"Map usage response -> snapshot\"]\n    I -- \"no\" --> L[\"Invalidate cache, surface OAuth error\"]\n```\n\n### OAuth (Now: `HEAD`)\n\n```mermaid\nflowchart TD\n    A[\"Claude OAuth fetch starts\"] --> B[\"hasCachedCredentials?\"]\n    B --> C{\"cached/refreshable creds exist?\"}\n    C -- \"yes\" --> D[\"loadWithAutoRefresh(allowKeychainPrompt=false unless bootstrap needed)\"]\n    C -- \"no\" --> E[\"Gate: should allow keychain prompt now?\"]\n    E --> F[\"loadWithAutoRefresh(allowKeychainPrompt=true if gate allows)\"]\n    D --> G{\"expired?\"}\n    F --> G\n    G -- \"no\" --> H[\"Use access token directly\"]\n    G -- \"yes\" --> I[\"POST /v1/oauth/token refresh_token grant\"]\n    I --> J{\"Refresh status\"}\n    J -- \"200\" --> K[\"Save refreshed creds to CodexBar keychain cache + memory\"]\n    J -- \"400/401 + invalid_grant\" --> L[\"Record terminal auth failure; block refresh until auth fingerprint changes\"]\n    J -- \"400/401 other\" --> M[\"Record transient failure; exponential backoff\"]\n    K --> H\n    H --> N{\"has user:profile scope?\"}\n    N -- \"no\" --> O[\"Fail: missing scope\"]\n    N -- \"yes\" --> P[\"GET /api/oauth/usage with Bearer access token\"]\n    P --> Q[\"Map usage response -> snapshot\"]\n```\n\n### OAuth Token Source Resolution (Now)\n\n```mermaid\nflowchart TD\n    A[\"load(...)\"] --> B[\"Environment token (CODEXBAR_CLAUDE_OAUTH_TOKEN)\"]\n    B -->|miss| C[\"Memory cache (valid + unexpired)\"]\n    C -->|miss| D[\"CodexBar keychain cache: com.steipete.codexbar.cache/oauth.claude\"]\n    D -->|miss| E[\"~/.claude/.credentials.json\"]\n    E -->|miss| F{\"allowKeychainPrompt && prompt gate open?\"}\n    F -- \"yes\" --> G[\"Claude keychain service: Claude Code-credentials (promptable fallback)\"]\n    F -- \"no\" --> H[\"Stop without keychain prompt path\"]\n```\n\n## Web Flow\n\n### Web (Before and Now: core fetch path largely unchanged)\n\n```mermaid\nflowchart TD\n    A[\"Claude Web fetch starts\"] --> B{\"manual cookie header configured?\"}\n    B -- \"yes\" --> C[\"Extract sessionKey from manual header\"]\n    B -- \"no\" --> D[\"Enumerate cookie import candidates\"]\n    D --> E[\"Try browser cookie import (claude.ai domain)\"]\n    E --> F{\"sessionKey found?\"}\n    C --> F\n    F -- \"no\" --> G[\"Fail: noSessionKeyFound\"]\n    F -- \"yes\" --> H[\"GET organizations + usage endpoints\"]\n    H --> I[\"Build web usage snapshot + identity + optional cost extras\"]\n```\n\n### Web Candidate Filtering + Prompt Implications (Now)\n\n```mermaid\nflowchart TD\n    A[\"cookieImportCandidates(...)\"] --> B{\"browser uses keychain for decryption?\"}\n    B -- \"no (Safari/Firefox/Zen)\" --> C[\"Keep candidate even if keychain disabled\"]\n    B -- \"yes (Chromium family)\" --> D{\"Keychain disabled?\"}\n    D -- \"yes\" --> E[\"Drop candidate\"]\n    D -- \"no\" --> F[\"Check BrowserCookieAccessGate cooldown\"]\n    F --> G{\"cooldown active?\"}\n    G -- \"yes\" --> E\n    G -- \"no\" --> H[\"Attempt import; on access denied record 6h cooldown\"]\n```\n\n## CLI Flow\n\n### CLI Fetch Path (Provider Runtime = `.cli`)\n\n```mermaid\nflowchart TD\n    A[\"CLI command (provider=claude)\"] --> B{\"source mode\"}\n    B -- \"auto\" --> C[\"Strategy order: web -> cli\"]\n    B -- \"web\" --> D[\"Web strategy only\"]\n    B -- \"cli\" --> E[\"CLI PTY strategy only\"]\n    B -- \"oauth\" --> F[\"OAuth strategy only\"]\n    E --> G[\"ClaudeStatusProbe via PTY session\"]\n    G --> H[\"Parse /usage output -> snapshot\"]\n```\n\nNotes:\n- The PTY parsing path itself is functionally stable in this range.\n- Claude CLI session environment still scrubs OAuth env overrides and `ANTHROPIC_*` vars before launching the subprocess.\n\n## App Runtime Auto Pipeline (Provider Fetch Plan)\n\n### App Auto Strategy Order (Descriptor Pipeline)\n\n```mermaid\nflowchart TD\n    A[\"App runtime source=auto\"] --> B[\"Try OAuth strategy first\"]\n    B -->|\"available + success\"| Z[\"Done\"]\n    B -->|\"unavailable or fallback on error\"| C[\"Try Web strategy\"]\n    C -->|\"success\"| Z\n    C -->|\"unavailable or fallback on error\"| D[\"Try CLI strategy\"]\n    D -->|\"success\"| Z\n    D -->|\"fail\"| E[\"Provider fetch failure\"]\n```\n\n### Internal `ClaudeUsageFetcher(dataSource:.auto)` Heuristic (Now)\n\n```mermaid\nflowchart TD\n    A[\"ClaudeUsageFetcher.loadLatestUsage(dataSource=.auto)\"] --> B[\"Probe OAuth creds non-interactively\"]\n    B --> C{\"usable OAuth creds?\"}\n    C -- \"yes\" --> D[\"Use OAuth path\"]\n    C -- \"no\" --> E{\"has web session?\"}\n    E -- \"yes\" --> F[\"Use Web path\"]\n    E -- \"no\" --> G{\"claude binary present?\"}\n    G -- \"yes\" --> H[\"Try CLI PTY, if fails then OAuth\"]\n    G -- \"no\" --> I[\"Fallback to OAuth\"]\n```\n\n## Keychain Prompt / Re-prompt Behavior\n\n### Claude OAuth Keychain Prompt Gate (Now)\n\n```mermaid\nstateDiagram-v2\n    [*] --> PromptAllowed\n    PromptAllowed --> CooldownBlocked: recordDenied()\n    CooldownBlocked --> CooldownBlocked: shouldAllowPrompt() before deniedUntil\n    CooldownBlocked --> PromptAllowed: time >= deniedUntil (6h)\n```\n\n### OAuth Refresh Failure Gate (Now)\n\n```mermaid\nstateDiagram-v2\n    [*] --> Open\n    Open --> TransientBackoff: recordTransientFailure (400/401 non-invalid_grant)\n    TransientBackoff --> Open: cooldown expires\n    TransientBackoff --> Open: auth fingerprint changes\n    Open --> TerminalBlocked: recordTerminalAuthFailure (invalid_grant)\n    TerminalBlocked --> TerminalBlocked: unchanged auth fingerprint\n    TerminalBlocked --> Open: auth fingerprint changes\n    TransientBackoff --> TerminalBlocked: terminal auth failure observed\n    TerminalBlocked --> Open: recordSuccess\n```\n\n## Key Differences in Permission Prompt Surfaces\n\n- Before:\n  - OAuth availability/load paths could reach Claude keychain access with less strict non-interactive protection.\n  - No dedicated Claude OAuth prompt cooldown gate.\n  - No terminal-vs-transient refresh failure gate.\n- Now:\n  - Non-interactive probes are stricter and reused broadly.\n  - Promptable Claude keychain access is gated and usually only bootstrap-oriented.\n  - Denials cause cooldown suppression to avoid repeated prompts.\n  - Refresh failures can suppress repeated attempts until either timeout (transient) or credential change (terminal).\n\n## Related Files\n\n- `/Users/ratulsarna/Developer/staipete/CodexBar/Sources/CodexBarCore/Providers/Claude/ClaudeUsageFetcher.swift`\n- `/Users/ratulsarna/Developer/staipete/CodexBar/Sources/CodexBarCore/Providers/Claude/ClaudeProviderDescriptor.swift`\n- `/Users/ratulsarna/Developer/staipete/CodexBar/Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthCredentials.swift`\n- `/Users/ratulsarna/Developer/staipete/CodexBar/Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthKeychainAccessGate.swift`\n- `/Users/ratulsarna/Developer/staipete/CodexBar/Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthRefreshFailureGate.swift`\n- `/Users/ratulsarna/Developer/staipete/CodexBar/Sources/CodexBarCore/KeychainAccessPreflight.swift`\n- `/Users/ratulsarna/Developer/staipete/CodexBar/Sources/CodexBarCore/KeychainNoUIQuery.swift`\n- `/Users/ratulsarna/Developer/staipete/CodexBar/Sources/CodexBarCore/BrowserCookieImportOrder.swift`\n- `/Users/ratulsarna/Developer/staipete/CodexBar/Sources/CodexBarCore/BrowserCookieAccessGate.swift`\n"
  },
  {
    "path": "docs/claude.md",
    "content": "---\nsummary: \"Claude provider data sources: OAuth API, web API (cookies), CLI PTY, and local cost usage.\"\nread_when:\n  - Debugging Claude usage/status parsing\n  - Updating Claude OAuth/web endpoints or cookie import\n  - Adjusting Claude CLI PTY automation\n  - Reviewing local cost usage scanning\n---\n\n# Claude provider\n\nClaude supports three usage data paths plus local cost usage. The main provider pipeline uses runtime-specific\nautomatic selection, but the codebase still has multiple active Claude `.auto` decision sites while the refactor is\npending. For the exact current-state parity contract, see\n[docs/refactor/claude-current-baseline.md](refactor/claude-current-baseline.md).\n\n## Data sources + selection order\n\n### Default selection (debug menu disabled)\n- App runtime main pipeline: OAuth API → CLI PTY → Web API.\n- CLI runtime main pipeline: Web API → CLI PTY.\n- Explicit picker modes (OAuth/Web/CLI) bypass automatic fallback.\n- A lower-level direct Claude fetcher still contains a separate `.auto` order. That inconsistency is tracked in\n  [docs/refactor/claude-current-baseline.md](refactor/claude-current-baseline.md).\n\nUsage source picker:\n- Preferences → Providers → Claude → Usage source (Auto/OAuth/Web/CLI).\n\n## Keychain prompt policy (Claude OAuth)\n- Preferences → Providers → Claude → Keychain prompt policy.\n- Options:\n  - `Never prompt`: never attempts interactive Claude OAuth Keychain prompts.\n  - `Only on user action` (default): interactive prompts are reserved for user-initiated repair flows.\n  - `Always allow prompts`: allows interactive prompts in both user and background flows.\n- This setting only affects Claude OAuth Keychain prompting behavior; it does not switch your Claude usage source.\n- If Preferences → Advanced → Disable Keychain access is enabled, this policy remains visible but inactive until\n  Keychain access is re-enabled.\n\n### Debug selection (debug menu enabled)\n- The Debug pane can force OAuth / Web / CLI.\n- Web extras are internal-only (not exposed in the Providers pane).\n\n## OAuth API (preferred)\n- Credentials:\n  - Keychain service: `Claude Code-credentials` (primary on macOS).\n  - File fallback: `~/.claude/.credentials.json`.\n- Requires `user:profile` scope (CLI tokens with only `user:inference` cannot call usage).\n- Endpoint:\n  - `GET https://api.anthropic.com/api/oauth/usage`\n- Headers:\n  - `Authorization: Bearer <access_token>`\n  - `anthropic-beta: oauth-2025-04-20`\n- Mapping:\n  - `five_hour` → session window.\n  - `seven_day` → weekly window.\n  - `seven_day_sonnet` / `seven_day_opus` → model-specific weekly window.\n  - `extra_usage` → Extra usage cost (monthly spend/limit).\n- Plan inference: `rate_limit_tier` from credentials maps to Max/Pro/Team/Enterprise.\n\n## Web API (cookies)\n- Preferences → Providers → Claude → Cookie source (Automatic or Manual).\n- Manual mode accepts a `Cookie:` header from a claude.ai request.\n- Multi-account manual tokens: add entries to `~/.codexbar/config.json` (`tokenAccounts`) and set Claude cookies to\n  Manual. The menu can show all accounts stacked or a switcher bar (Preferences → Advanced → Display).\n- Claude token accounts accept either `sessionKey` cookies or OAuth access tokens (`sk-ant-oat...`). OAuth-token\n  accounts route to the OAuth path and disable cookie mode; session-key or cookie-header accounts stay in manual\n  cookie mode. The exact edge-routing rules are documented in\n  [docs/refactor/claude-current-baseline.md](refactor/claude-current-baseline.md).\n- Cookie source order:\n  1) Safari: `~/Library/Cookies/Cookies.binarycookies`\n  2) Chrome/Chromium forks: `~/Library/Application Support/Google/Chrome/*/Cookies`\n  3) Firefox: `~/Library/Application Support/Firefox/Profiles/*/cookies.sqlite`\n- Domain: `claude.ai`.\n- Cookie name required:\n  - `sessionKey` (value prefix `sk-ant-...`).\n- Cached cookies: Keychain cache `com.steipete.codexbar.cache` (account `cookie.claude`, source + timestamp).\n  Reused before re-importing from browsers.\n- API calls (all include `Cookie: sessionKey=<value>`):\n  - `GET https://claude.ai/api/organizations` → org UUID.\n  - `GET https://claude.ai/api/organizations/{orgId}/usage` → session/weekly/opus.\n  - `GET https://claude.ai/api/organizations/{orgId}/overage_spend_limit` → Extra usage spend/limit.\n  - `GET https://claude.ai/api/account` → email + plan hints.\n- Outputs:\n  - Session + weekly + model-specific percent used.\n  - Extra usage spend/limit (if enabled).\n  - Account email + inferred plan.\n\n## CLI PTY (fallback)\n- Runs `claude` in a PTY session (`ClaudeCLISession`).\n- Default behavior: exit after each probe; Debug → \"Keep CLI sessions alive\" keeps it running between probes.\n- Command flow:\n  1) Start CLI with `--allowed-tools \"\"` (no tools).\n  2) Auto-respond to first-run prompts (trust files, workspace, telemetry).\n  3) Send `/usage`, wait for rendered panel; send Enter retries if needed.\n  4) Optionally send `/status` to extract identity fields.\n- Parsing (`ClaudeStatusProbe`):\n  - Strips ANSI, locates \"Current session\" + \"Current week\" headers.\n  - Extracts percent left/used and reset text near those headers.\n  - Parses `Account:` and `Org:` lines when present.\n  - Surfaces CLI errors (e.g. token expired) directly.\n\n## Cost usage (local log scan)\n- Source roots:\n  - `$CLAUDE_CONFIG_DIR` (comma-separated), each root uses `<root>/projects`.\n  - Fallback roots:\n    - `~/.config/claude/projects`\n    - `~/.claude/projects`\n- Files: `**/*.jsonl` under the project roots.\n- Parsing:\n  - Lines with `type: \"assistant\"` and `message.usage`.\n  - Uses per-model token counts (input, cache read/create, output).\n  - Deduplicates streaming chunks by `message.id + requestId` (usage is cumulative per chunk).\n- Cache:\n  - `~/Library/Caches/CodexBar/cost-usage/claude-v1.json`\n\n## Key files\n- OAuth: `Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/*`\n- Web API: `Sources/CodexBarCore/Providers/Claude/ClaudeWeb/ClaudeWebAPIFetcher.swift`\n- CLI PTY: `Sources/CodexBarCore/Providers/Claude/ClaudeStatusProbe.swift`,\n  `Sources/CodexBarCore/Providers/Claude/ClaudeCLISession.swift`\n- Cost usage: `Sources/CodexBarCore/CostUsageFetcher.swift`,\n  `Sources/CodexBarCore/Vendored/CostUsage/*`\n"
  },
  {
    "path": "docs/cli.md",
    "content": "---\nsummary: \"CodexBar CLI for fetching usage from the command line.\"\nread_when:\n  - \"You want to call CodexBar data from scripts or a terminal.\"\n  - \"Adding or modifying Commander-based CLI commands.\"\n  - \"Aligning menubar and CLI output/behavior.\"\n---\n\n# CodexBar CLI\n\nA lightweight Commander-based CLI that mirrors the menubar app’s data paths (Codex web/RPC → PTY fallback; Claude web by default with CLI fallback and OAuth debug).\nUse it when you need usage numbers in scripts, CI, or dashboards without UI.\n\n## Install\n- In the app: **Preferences → Advanced → Install CLI**. This symlinks `CodexBarCLI` to `/usr/local/bin/codexbar` and `/opt/homebrew/bin/codexbar`.\n- From the repo: `./bin/install-codexbar-cli.sh` (same symlink targets).\n- Manual: `ln -sf \"/Applications/CodexBar.app/Contents/Helpers/CodexBarCLI\" /usr/local/bin/codexbar`.\n\n### Linux install\n- Homebrew (Linuxbrew, Linux only): `brew install steipete/tap/codexbar`.\n- Download `CodexBarCLI-v<tag>-linux-<arch>.tar.gz` from GitHub Releases (x86_64 + aarch64).\n- Extract; run `./codexbar` (symlink) or `./CodexBarCLI`.\n\n```\ntar -xzf CodexBarCLI-v0.17.0-linux-x86_64.tar.gz\n./codexbar --version\n./codexbar usage --format json --pretty\n```\n\n## Build\n- `./Scripts/package_app.sh` (or `./Scripts/compile_and_run.sh`) bundles `CodexBarCLI` into `CodexBar.app/Contents/Helpers/CodexBarCLI`.\n- Standalone: `swift build -c release --product CodexBarCLI` (binary at `./.build/release/CodexBarCLI`).\n- Dependencies: Swift 6.2+, Commander package (`https://github.com/steipete/Commander`).\n\n## Configuration\nCodexBar reads `~/.codexbar/config.json` for provider settings, secrets, and ordering.\nSee `docs/configuration.md` for the schema.\n\n## Command\n- `codexbar` defaults to the `usage` command.\n  - `--format text|json` (default: text).\n- `codexbar cost` prints local token cost usage (Claude + Codex) without web/CLI access.\n  - `--format text|json` (default: text).\n  - `--refresh` ignores cached scans.\n- `--provider <id|both|all>` (default: enabled providers in config; falls back to defaults when missing).\n  - Provider IDs live in the config file (see `docs/configuration.md`).\n  - `--account <label>` / `--account-index <n>` / `--all-accounts` (token accounts from config; requires a single provider).\n  - `--no-credits` (hide Codex credits in text output).\n  - `--pretty` (pretty-print JSON).\n  - `--status` (fetch provider status pages and include them in output).\n  - `--antigravity-plan-debug` (debug: print Antigravity planInfo fields to stderr).\n- `--source <auto|web|cli|oauth|api>` (default: `auto`).\n    - `auto` (macOS only): uses browser cookies for Codex + Claude, with CLI fallback only when cookies are missing.\n    - `web` (macOS only): web-only; no CLI fallback.\n    - `cli`: CLI-only (Codex RPC → PTY fallback; Claude PTY).\n    - `oauth`: Claude OAuth only (debug); no fallback. Not supported for Codex.\n    - `api`: API key flow when the provider supports it (z.ai, Gemini, Copilot, Kilo, Kimi K2, MiniMax, Warp, OpenRouter, Synthetic).\n    - Output `source` reflects the strategy actually used (`openai-web`, `web`, `oauth`, `api`, `local`, or provider CLI label).\n    - Codex web: OpenAI web dashboard (usage limits, credits remaining, code review remaining, usage breakdown).\n        - `--web-timeout <seconds>` (default: 60)\n        - `--web-debug-dump-html` (writes HTML snapshots to `/tmp` when data is missing)\n    - Claude web: claude.ai API (session + weekly usage, plus account metadata when available).\n    - Kilo auto: app.kilo.ai API first, then CLI auth fallback (`~/.local/share/kilo/auth.json`) on missing/unauthorized API credentials.\n    - Linux: `web/auto` are not supported; CLI prints an error and exits non-zero.\n- Global flags: `-h/--help`, `-V/--version`, `-v/--verbose`, `--no-color`, `--log-level <trace|verbose|debug|info|warning|error|critical>`, `--json-output`, `--json-only`.\n  - `--json-output`: JSONL logs on stderr (machine-readable).\n  - `--json-only`: suppress non-JSON output; errors become JSON payloads.\n- `codexbar config validate` checks `~/.codexbar/config.json` for invalid fields.\n  - `--format text|json`, `--pretty`, and `--json-only` are supported.\n  - Warnings keep exit code 0; errors exit non-zero.\n- `codexbar config dump` prints the normalized config JSON.\n\n### Token accounts\nThe CLI reads multi-account tokens from `~/.codexbar/config.json` (same file as the app).\n- Select a specific account: `--account <label>` (matches the label/email in the file).\n- Select by index (1-based): `--account-index <n>`.\n- Fetch all accounts for the provider: `--all-accounts`.\nAccount selection flags require a single provider (`--provider claude`, etc.).\nFor Claude, token accounts accept either `sessionKey` cookies or OAuth access tokens (`sk-ant-oat...`).\nOAuth usage requires the `user:profile` scope; inference-only tokens will return an error.\n\n### Cost JSON payload\n`codexbar cost --format json` emits an array of payloads (one per provider).\n- `provider`, `source`, `updatedAt`\n- `sessionTokens`, `sessionCostUSD`\n- `last30DaysTokens`, `last30DaysCostUSD`\n- `daily[]`: `date`, `inputTokens`, `outputTokens`, `cacheReadTokens`, `cacheCreationTokens`, `totalTokens`, `totalCost`, `modelsUsed`, `modelBreakdowns[]` (`modelName`, `cost`)\n- `totals`: `inputTokens`, `outputTokens`, `cacheReadTokens`, `cacheCreationTokens`, `totalTokens`, `totalCost`\n\n## Example usage\n```\ncodexbar                          # text, respects app toggles\ncodexbar --provider claude        # force Claude\ncodexbar --provider all           # query all providers (honors your logins/toggles)\ncodexbar --format json --pretty   # machine output\ncodexbar --format json --provider both\ncodexbar cost                     # local cost usage (last 30 days + today)\ncodexbar cost --provider claude --format json --pretty\nCOPILOT_API_TOKEN=... codexbar --provider copilot --format json --pretty\ncodexbar --status                 # include status page indicator/description\ncodexbar --provider codex --source web --format json --pretty\ncodexbar --provider claude --account steipete@gmail.com\ncodexbar --provider claude --all-accounts --format json --pretty\ncodexbar --json-only --format json --pretty\ncodexbar --provider gemini --source api --format json --pretty\nKILO_API_KEY=... codexbar --provider kilo --source api --format json --pretty\ncodexbar config validate --format json --pretty\ncodexbar config dump --pretty\n```\n\n### Sample output (text)\n```\n== Codex 0.6.0 (codex-cli) ==\nSession: 72% left [========----]\nResets today at 2:15 PM\nWeekly: 41% left [====--------]\nPace: 6% in reserve | Expected 47% used | Lasts until reset\nResets Fri at 9:00 AM\nCredits: 112.4 left\n\n== Claude Code 2.0.58 (web) ==\nSession: 88% left [==========--]\nResets tomorrow at 1:00 AM\nWeekly: 63% left [=======-----]\nPace: On pace | Expected 37% used | Runs out in 4d\nResets Sat at 6:00 AM\nSonnet: 95% left [===========-]\nAccount: user@example.com\nPlan: Pro\n\n== Kilo (cli) ==\nCredits: 60% left [=======-----]\n40/100 credits\nPlan: Kilo Pass Pro\nActivity: Auto top-up: visa\nNote: Using CLI fallback\n```\n\n### Sample output (JSON, pretty)\n```json\n{\n  \"provider\": \"codex\",\n  \"version\": \"0.6.0\",\n  \"source\": \"openai-web\",\n  \"status\": { \"indicator\": \"none\", \"description\": \"Operational\", \"updatedAt\": \"2025-12-04T17:55:00Z\", \"url\": \"https://status.openai.com/\" },\n  \"usage\": {\n    \"primary\": { \"usedPercent\": 28, \"windowMinutes\": 300, \"resetsAt\": \"2025-12-04T19:15:00Z\" },\n    \"secondary\": { \"usedPercent\": 59, \"windowMinutes\": 10080, \"resetsAt\": \"2025-12-05T17:00:00Z\" },\n    \"tertiary\": null,\n    \"updatedAt\": \"2025-12-04T18:10:22Z\",\n    \"identity\": {\n      \"providerID\": \"codex\",\n      \"accountEmail\": \"user@example.com\",\n      \"accountOrganization\": null,\n      \"loginMethod\": \"plus\"\n    },\n    \"accountEmail\": \"user@example.com\",\n    \"accountOrganization\": null,\n    \"loginMethod\": \"plus\"\n  },\n  \"credits\": { \"remaining\": 112.4, \"updatedAt\": \"2025-12-04T18:10:21Z\" },\n  \"antigravityPlanInfo\": null,\n  \"openaiDashboard\": {\n    \"signedInEmail\": \"user@example.com\",\n    \"codeReviewRemainingPercent\": 100,\n    \"creditEvents\": [\n      { \"id\": \"00000000-0000-0000-0000-000000000000\", \"date\": \"2025-12-04T00:00:00Z\", \"service\": \"CLI\", \"creditsUsed\": 123.45 }\n    ],\n    \"dailyBreakdown\": [\n      {\n        \"day\": \"2025-12-04\",\n        \"services\": [{ \"service\": \"CLI\", \"creditsUsed\": 123.45 }],\n        \"totalCreditsUsed\": 123.45\n      }\n    ],\n    \"updatedAt\": \"2025-12-04T18:10:21Z\"\n  }\n}\n```\n\n## Exit codes\n- 0: success\n- 2: provider missing (binary not on PATH)\n- 3: parse/format error\n- 4: CLI timeout\n- 1: unexpected failure\n\n## Notes\n- CLI uses the config file for enabled providers, ordering, and secrets.\n- Reset lines follow the in-app reset time display setting when available (default: countdown).\n- Text output uses ANSI colors when stdout is a rich TTY; disable with `--no-color` or `NO_COLOR`/`TERM=dumb`.\n- Copilot CLI queries require an API token via config `apiKey` or `COPILOT_API_TOKEN`.\n- Prefer Codex RPC first, then PTY fallback; Claude defaults to web with CLI fallback when cookies are missing.\n- Kilo text output splits identity into `Plan:` and `Activity:` lines; in `--source auto`, resolved CLI fetches add\n  `Note: Using CLI fallback`.\n- Kilo auto-mode failures include a fallback-attempt summary line in text mode (API attempt then CLI attempt).\n- OpenAI web requires a signed-in `chatgpt.com` session in Safari, Chrome, or Firefox. No passwords are stored; CodexBar reuses cookies.\n- Safari cookie import may require granting CodexBar Full Disk Access (System Settings → Privacy & Security → Full Disk Access).\n- The `openaiDashboard` JSON field is normally sourced from the app’s cached dashboard snapshot; `--source auto|web` refreshes it live via WebKit using a per-account cookie store.\n- Future: optional `--from-cache` flag to read the menubar app’s persisted snapshot (if/when that file lands).\n"
  },
  {
    "path": "docs/codex-oauth.md",
    "content": "---\nsummary: \"Codex OAuth resolver: tokens, refresh, usage endpoint, and fetch strategy wiring.\"\nread_when:\n  - Adding or modifying Codex OAuth usage fetching\n  - Debugging auth.json parsing or token refresh behavior\n  - Adjusting Codex provider source selection\n---\n\n# Codex OAuth Resolver Implementation Plan\n\n> Replicate Codex's direct OAuth token usage in CodexBar instead of calling the CLI.\n\n## Background\n\nCurrently, CodexBar fetches Codex usage by:\n1. Running `codex` CLI in PTY mode\n2. Sending `/status` command\n3. Parsing the text output\n\nThis is slow and unreliable. The goal is to directly read Codex's OAuth tokens and call the same API endpoints that Codex uses internally.\n\n---\n\n## Codex OAuth Architecture (from source analysis)\n\n### Token Storage\n\n**Location:** `~/.codex/auth.json`\n\n```json\n{\n  \"OPENAI_API_KEY\": null,\n  \"tokens\": {\n    \"id_token\": \"eyJ...\",\n    \"access_token\": \"eyJ...\",\n    \"refresh_token\": \"...\",\n    \"account_id\": \"account-...\"\n  },\n  \"last_refresh\": \"2025-12-28T12:34:56Z\"\n}\n```\n\n**Source:** `codex-rs/core/src/auth/storage.rs`\n\n### Token Refresh\n\n**Endpoint:** `POST https://auth.openai.com/oauth/token`\n\n**Request:**\n```json\n{\n  \"client_id\": \"app_EMoamEEZ73f0CkXaXp7hrann\",\n  \"grant_type\": \"refresh_token\",\n  \"refresh_token\": \"<refresh_token>\",\n  \"scope\": \"openid profile email\"\n}\n```\n\n**Response:**\n```json\n{\n  \"id_token\": \"eyJ...\",\n  \"access_token\": \"eyJ...\",\n  \"refresh_token\": \"...\"\n}\n```\n\n**Refresh Interval:** 8 days (from `TOKEN_REFRESH_INTERVAL` constant)\n\n**Source:** `codex-rs/core/src/auth.rs:504-545`\n\n### Usage API\n\n**Endpoint:** `GET {chatgpt_base_url}/wham/usage` (default: `https://chatgpt.com/backend-api/wham/usage`)\n\nIf `chatgpt_base_url` does not include `/backend-api`, Codex falls back to\n`{base_url}/api/codex/usage` (see `PathStyle` in `backend-client/src/client.rs`).\n\n**Headers:**\n```\nAuthorization: Bearer <access_token>\nChatGPT-Account-Id: <account_id>\nUser-Agent: codex-cli\n```\n\n**Quick checks**\n- Command: `cat ~/.codex/auth.json`\n- Command: `curl -H \"Authorization: Bearer <access_token>\" -H \"ChatGPT-Account-Id: <account_id>\" -H \"User-Agent: codex-cli\" https://chatgpt.com/backend-api/wham/usage`\n- Command: `CodexBarCLI usage --provider codex --source oauth --json --pretty`\n\n**Response:**\n```json\n{\n  \"plan_type\": \"pro\",\n  \"rate_limit\": {\n    \"primary_window\": {\n      \"used_percent\": 15,\n      \"reset_at\": 1735401600,\n      \"limit_window_seconds\": 18000\n    },\n    \"secondary_window\": {\n      \"used_percent\": 5,\n      \"reset_at\": 1735920000,\n      \"limit_window_seconds\": 604800\n    }\n  },\n  \"credits\": {\n    \"has_credits\": true,\n    \"unlimited\": false,\n    \"balance\": 150.0\n  }\n}\n```\n\n**Source:** `codex-rs/backend-client/src/client.rs:161-170`\n\n---\n\n## Implementation\n\n### Files to Create\n\n| File | Location | Purpose |\n|------|----------|---------|\n| `CodexOAuthCredentials.swift` | `Sources/CodexBarCore/Providers/Codex/CodexOAuth/` | Token storage model + loader |\n| `CodexOAuthUsageFetcher.swift` | `Sources/CodexBarCore/Providers/Codex/CodexOAuth/` | API client for usage endpoint |\n| `CodexTokenRefresher.swift` | `Sources/CodexBarCore/Providers/Codex/CodexOAuth/` | Token refresh logic |\n\n### Files to Modify\n\n| File | Changes |\n|------|---------|\n| `CodexProviderDescriptor.swift` | Add `CodexOAuthFetchStrategy`, update `resolveStrategies()` |\n\n---\n\n### Step 1: CodexOAuthCredentials.swift\n\n```swift\nimport Foundation\n\npublic struct CodexOAuthCredentials: Sendable {\n    public let accessToken: String\n    public let refreshToken: String\n    public let idToken: String?\n    public let accountId: String?\n    public let lastRefresh: Date?\n\n    public var needsRefresh: Bool {\n        guard let last = lastRefresh else { return true }\n        let eightDays: TimeInterval = 8 * 24 * 3600\n        return Date().timeIntervalSince(last) > eightDays\n    }\n}\n\npublic enum CodexOAuthCredentialsError: LocalizedError {\n    case notFound\n    case decodeFailed(String)\n    case missingTokens\n\n    public var errorDescription: String? {\n        switch self {\n        case .notFound:\n            \"Codex auth.json not found. Run `codex` to log in.\"\n        case .decodeFailed(let msg):\n            \"Failed to decode Codex credentials: \\(msg)\"\n        case .missingTokens:\n            \"Codex auth.json exists but contains no tokens.\"\n        }\n    }\n}\n\npublic enum CodexOAuthCredentialsStore {\n    private static var authFilePath: URL {\n        let home = FileManager.default.homeDirectoryForCurrentUser\n        // Respect CODEX_HOME if set\n        if let codexHome = ProcessInfo.processInfo.environment[\"CODEX_HOME\"],\n           !codexHome.isEmpty {\n            return URL(fileURLWithPath: codexHome).appendingPathComponent(\"auth.json\")\n        }\n        return home.appendingPathComponent(\".codex/auth.json\")\n    }\n\n    public static func load() throws -> CodexOAuthCredentials {\n        let url = authFilePath\n        guard FileManager.default.fileExists(atPath: url.path) else {\n            throw CodexOAuthCredentialsError.notFound\n        }\n\n        let data = try Data(contentsOf: url)\n        guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {\n            throw CodexOAuthCredentialsError.decodeFailed(\"Invalid JSON\")\n        }\n\n        // Check for API key auth (no tokens needed for refresh)\n        if let apiKey = json[\"OPENAI_API_KEY\"] as? String, !apiKey.isEmpty {\n            return CodexOAuthCredentials(\n                accessToken: apiKey,\n                refreshToken: \"\",\n                idToken: nil,\n                accountId: nil,\n                lastRefresh: nil)\n        }\n\n        guard let tokens = json[\"tokens\"] as? [String: Any],\n              let accessToken = tokens[\"access_token\"] as? String,\n              let refreshToken = tokens[\"refresh_token\"] as? String else {\n            throw CodexOAuthCredentialsError.missingTokens\n        }\n\n        let idToken = tokens[\"id_token\"] as? String\n        let accountId = tokens[\"account_id\"] as? String\n\n        let lastRefresh: Date? = {\n            guard let str = json[\"last_refresh\"] as? String else { return nil }\n            let formatter = ISO8601DateFormatter()\n            formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]\n            return formatter.date(from: str) ?? ISO8601DateFormatter().date(from: str)\n        }()\n\n        return CodexOAuthCredentials(\n            accessToken: accessToken,\n            refreshToken: refreshToken,\n            idToken: idToken,\n            accountId: accountId,\n            lastRefresh: lastRefresh)\n    }\n\n    public static func save(_ credentials: CodexOAuthCredentials) throws {\n        let url = authFilePath\n\n        // Read existing file to preserve structure\n        var json: [String: Any] = [:]\n        if let data = try? Data(contentsOf: url),\n           let existing = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {\n            json = existing\n        }\n\n        var tokens: [String: Any] = [\n            \"access_token\": credentials.accessToken,\n            \"refresh_token\": credentials.refreshToken\n        ]\n        if let idToken = credentials.idToken {\n            tokens[\"id_token\"] = idToken\n        }\n        if let accountId = credentials.accountId {\n            tokens[\"account_id\"] = accountId\n        }\n\n        json[\"tokens\"] = tokens\n        json[\"last_refresh\"] = ISO8601DateFormatter().string(from: Date())\n\n        let data = try JSONSerialization.data(withJSONObject: json, options: [.prettyPrinted, .sortedKeys])\n        try data.write(to: url, options: .atomic)\n    }\n}\n```\n\n---\n\n### Step 2: CodexOAuthUsageFetcher.swift\n\n```swift\nimport Foundation\n\npublic struct CodexUsageResponse: Decodable, Sendable {\n    public let planType: PlanType\n    public let rateLimit: RateLimitDetails?\n    public let credits: CreditDetails?\n\n    enum CodingKeys: String, CodingKey {\n        case planType = \"plan_type\"\n        case rateLimit = \"rate_limit\"\n        case credits\n    }\n\n    public enum PlanType: String, Decodable, Sendable {\n        case guest, free, go, plus, pro\n        case freeWorkspace = \"free_workspace\"\n        case team, business, education, quorum, k12, enterprise, edu\n    }\n\n    public struct RateLimitDetails: Decodable, Sendable {\n        public let primaryWindow: WindowSnapshot?\n        public let secondaryWindow: WindowSnapshot?\n\n        enum CodingKeys: String, CodingKey {\n            case primaryWindow = \"primary_window\"\n            case secondaryWindow = \"secondary_window\"\n        }\n    }\n\n    public struct WindowSnapshot: Decodable, Sendable {\n        public let usedPercent: Int\n        public let resetAt: Int\n        public let limitWindowSeconds: Int\n\n        enum CodingKeys: String, CodingKey {\n            case usedPercent = \"used_percent\"\n            case resetAt = \"reset_at\"\n            case limitWindowSeconds = \"limit_window_seconds\"\n        }\n    }\n\n    public struct CreditDetails: Decodable, Sendable {\n        public let hasCredits: Bool\n        public let unlimited: Bool\n        public let balance: Double?\n\n        enum CodingKeys: String, CodingKey {\n            case hasCredits = \"has_credits\"\n            case unlimited\n            case balance\n        }\n    }\n}\n\npublic enum CodexOAuthFetchError: LocalizedError, Sendable {\n    case unauthorized\n    case invalidResponse\n    case serverError(Int, String?)\n    case networkError(Error)\n\n    public var errorDescription: String? {\n        switch self {\n        case .unauthorized:\n            \"Codex OAuth token expired or invalid. Run `codex` to re-authenticate.\"\n        case .invalidResponse:\n            \"Invalid response from Codex usage API.\"\n        case .serverError(let code, let msg):\n            \"Codex API error \\(code): \\(msg ?? \"unknown\")\"\n        case .networkError(let error):\n            \"Network error: \\(error.localizedDescription)\"\n        }\n    }\n}\n\npublic enum CodexOAuthUsageFetcher {\n    private static let defaultChatGPTBaseURL = \"https://chatgpt.com/backend-api/\"\n    private static let chatGPTUsagePath = \"/wham/usage\"\n    private static let codexUsagePath = \"/api/codex/usage\"\n\n    public static func fetchUsage(\n        accessToken: String,\n        accountId: String?\n    ) async throws -> CodexUsageResponse {\n        var request = URLRequest(url: resolveUsageURL())\n        request.httpMethod = \"GET\"\n        request.setValue(\"Bearer \\(accessToken)\", forHTTPHeaderField: \"Authorization\")\n        request.setValue(\"CodexBar\", forHTTPHeaderField: \"User-Agent\")\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Accept\")\n\n        if let accountId {\n            request.setValue(accountId, forHTTPHeaderField: \"ChatGPT-Account-Id\")\n        }\n\n        let (data, response): (Data, URLResponse)\n        do {\n            (data, response) = try await URLSession.shared.data(for: request)\n        } catch {\n            throw CodexOAuthFetchError.networkError(error)\n        }\n\n        guard let http = response as? HTTPURLResponse else {\n            throw CodexOAuthFetchError.invalidResponse\n        }\n\n        switch http.statusCode {\n        case 200...299:\n            do {\n                return try JSONDecoder().decode(CodexUsageResponse.self, from: data)\n            } catch {\n                throw CodexOAuthFetchError.invalidResponse\n            }\n        case 401, 403:\n            throw CodexOAuthFetchError.unauthorized\n        default:\n            let body = String(data: data, encoding: .utf8)\n            throw CodexOAuthFetchError.serverError(http.statusCode, body)\n        }\n    }\n}\n```\n\n---\n\n### Step 3: CodexTokenRefresher.swift\n\n```swift\nimport Foundation\n\npublic enum CodexTokenRefresher {\n    private static let refreshEndpoint = URL(string: \"https://auth.openai.com/oauth/token\")!\n    private static let clientID = \"app_EMoamEEZ73f0CkXaXp7hrann\"\n\n    public enum RefreshError: LocalizedError {\n        case expired\n        case revoked\n        case reused\n        case networkError(Error)\n        case invalidResponse(String)\n\n        public var errorDescription: String? {\n            switch self {\n            case .expired:\n                \"Refresh token expired. Please run `codex` to log in again.\"\n            case .revoked:\n                \"Refresh token was revoked. Please run `codex` to log in again.\"\n            case .reused:\n                \"Refresh token was already used. Please run `codex` to log in again.\"\n            case .networkError(let error):\n                \"Network error during token refresh: \\(error.localizedDescription)\"\n            case .invalidResponse(let msg):\n                \"Invalid refresh response: \\(msg)\"\n            }\n        }\n    }\n\n    public static func refresh(_ credentials: CodexOAuthCredentials) async throws -> CodexOAuthCredentials {\n        guard !credentials.refreshToken.isEmpty else {\n            // API key auth - no refresh needed\n            return credentials\n        }\n\n        var request = URLRequest(url: refreshEndpoint)\n        request.httpMethod = \"POST\"\n        request.setValue(\"application/json\", forHTTPHeaderField: \"Content-Type\")\n\n        let body: [String: String] = [\n            \"client_id\": clientID,\n            \"grant_type\": \"refresh_token\",\n            \"refresh_token\": credentials.refreshToken,\n            \"scope\": \"openid profile email\"\n        ]\n        request.httpBody = try JSONSerialization.data(withJSONObject: body)\n\n        let (data, response): (Data, URLResponse)\n        do {\n            (data, response) = try await URLSession.shared.data(for: request)\n        } catch {\n            throw RefreshError.networkError(error)\n        }\n\n        guard let http = response as? HTTPURLResponse else {\n            throw RefreshError.invalidResponse(\"No HTTP response\")\n        }\n\n        if http.statusCode == 401 {\n            // Parse error code to classify failure\n            if let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],\n               let errorCode = (json[\"error\"] as? [String: Any])?[\"code\"] as? String\n                              ?? json[\"error\"] as? String\n                              ?? json[\"code\"] as? String {\n                switch errorCode.lowercased() {\n                case \"refresh_token_expired\": throw RefreshError.expired\n                case \"refresh_token_reused\": throw RefreshError.reused\n                case \"refresh_token_invalidated\": throw RefreshError.revoked\n                default: throw RefreshError.expired\n                }\n            }\n            throw RefreshError.expired\n        }\n\n        guard http.statusCode == 200 else {\n            throw RefreshError.invalidResponse(\"Status \\(http.statusCode)\")\n        }\n\n        guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {\n            throw RefreshError.invalidResponse(\"Invalid JSON\")\n        }\n\n        let newAccessToken = json[\"access_token\"] as? String ?? credentials.accessToken\n        let newRefreshToken = json[\"refresh_token\"] as? String ?? credentials.refreshToken\n        let newIdToken = json[\"id_token\"] as? String ?? credentials.idToken\n\n        return CodexOAuthCredentials(\n            accessToken: newAccessToken,\n            refreshToken: newRefreshToken,\n            idToken: newIdToken,\n            accountId: credentials.accountId,\n            lastRefresh: Date())\n    }\n}\n```\n\n---\n\n### Step 4: Update CodexProviderDescriptor.swift\n\nAdd OAuth to `sourceModes` and create new strategy:\n\n```swift\n// In makeDescriptor(), update fetchPlan:\nfetchPlan: ProviderFetchPlan(\n    sourceModes: [.auto, .oauth, .web, .cli],  // Add .oauth\n    pipeline: ProviderFetchPipeline(resolveStrategies: self.resolveStrategies)),\n\n// Update resolveStrategies:\nprivate static func resolveStrategies(context: ProviderFetchContext) async -> [any ProviderFetchStrategy] {\n    let oauth = CodexOAuthFetchStrategy()\n    let cli = CodexCLIUsageStrategy()\n    let web = CodexWebDashboardStrategy()\n\n    switch context.sourceMode {\n    case .oauth:\n        return [oauth]\n    case .web:\n        return [web]\n    case .cli:\n        return [cli]\n    case .auto:\n        // OAuth first (fast), CLI fallback\n        if context.runtime == .cli {\n            return [web, cli]\n        }\n        return [oauth, cli]\n    }\n}\n\n// Add new strategy:\nstruct CodexOAuthFetchStrategy: ProviderFetchStrategy {\n    let id: String = \"codex.oauth\"\n    let kind: ProviderFetchKind = .oauth\n\n    func isAvailable(_ context: ProviderFetchContext) async -> Bool {\n        (try? CodexOAuthCredentialsStore.load()) != nil\n    }\n\n    func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {\n        var creds = try CodexOAuthCredentialsStore.load()\n\n        // Refresh if needed (8+ days old)\n        if creds.needsRefresh && !creds.refreshToken.isEmpty {\n            creds = try await CodexTokenRefresher.refresh(creds)\n            try CodexOAuthCredentialsStore.save(creds)\n        }\n\n        let usage = try await CodexOAuthUsageFetcher.fetchUsage(\n            accessToken: creds.accessToken,\n            accountId: creds.accountId)\n\n        return makeResult(\n            usage: Self.mapUsage(usage),\n            credits: Self.mapCredits(usage.credits),\n            sourceLabel: \"oauth\")\n    }\n\n    func shouldFallback(on error: Error, context: ProviderFetchContext) -> Bool {\n        // Fallback to CLI on auth errors\n        if let fetchError = error as? CodexOAuthFetchError {\n            switch fetchError {\n            case .unauthorized: return true\n            default: return false\n            }\n        }\n        if error is CodexOAuthCredentialsError { return true }\n        if error is CodexTokenRefresher.RefreshError { return true }\n        return false\n    }\n\n    private static func mapUsage(_ response: CodexUsageResponse) -> UsageSnapshot {\n        let primary: RateWindow? = response.rateLimit?.primaryWindow.map { window in\n            RateWindow(\n                usedPercent: Double(window.usedPercent),\n                windowMinutes: window.limitWindowSeconds / 60,\n                resetsAt: Date(timeIntervalSince1970: TimeInterval(window.resetAt)),\n                resetDescription: nil)\n        }\n\n        let secondary: RateWindow? = response.rateLimit?.secondaryWindow.map { window in\n            RateWindow(\n                usedPercent: Double(window.usedPercent),\n                windowMinutes: window.limitWindowSeconds / 60,\n                resetsAt: Date(timeIntervalSince1970: TimeInterval(window.resetAt)),\n                resetDescription: nil)\n        }\n\n        let identity = ProviderIdentitySnapshot(\n            providerID: .codex,\n            accountEmail: nil,\n            accountOrganization: nil,\n            loginMethod: response.planType.rawValue)\n\n        return UsageSnapshot(\n            primary: primary ?? RateWindow(usedPercent: 0, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            secondary: secondary,\n            tertiary: nil,\n            providerCost: nil,\n            updatedAt: Date(),\n            identity: identity)\n    }\n\n    private static func mapCredits(_ credits: CodexUsageResponse.CreditDetails?) -> CreditsSnapshot? {\n        guard let credits else { return nil }\n        return CreditsSnapshot(\n            hasCredits: credits.hasCredits,\n            unlimited: credits.unlimited,\n            balance: credits.balance)\n    }\n}\n```\n\n---\n\n## Constants Reference\n\n| Constant | Value | Source |\n|----------|-------|--------|\n| Client ID | `app_EMoamEEZ73f0CkXaXp7hrann` | `auth.rs:618` |\n| Refresh URL | `https://auth.openai.com/oauth/token` | `auth.rs:66` |\n| Usage URL | `https://chatgpt.com/backend-api/wham/usage` (default) | `client.rs:163` |\n| Token refresh interval | 8 days | `auth.rs:59` |\n| Auth file | `~/.codex/auth.json` | `storage.rs` |\n\n---\n\n## Testing\n\n1. Ensure `~/.codex/auth.json` exists (run `codex` to log in first)\n2. Run CodexBar with debug logging enabled\n3. Verify OAuth strategy is selected and API calls succeed\n4. Test token refresh by manually setting `last_refresh` to old date\n5. Test fallback by temporarily renaming auth.json\n\n---\n\n## Error Handling\n\n| Error | Behavior |\n|-------|----------|\n| No auth.json | Fall back to CLI strategy |\n| Token expired | Attempt refresh, fall back to CLI on failure |\n| Refresh failed | Log error, fall back to CLI |\n| API error | Fall back to CLI |\n| Network error | Retry with backoff, then fall back |\n"
  },
  {
    "path": "docs/codex.md",
    "content": "---\nsummary: \"Codex provider data sources: OpenAI web dashboard, Codex CLI RPC/PTY, credits, and local cost usage.\"\nread_when:\n  - Debugging Codex usage/credits parsing\n  - Updating OpenAI dashboard scraping or cookie import\n  - Changing Codex CLI RPC/PTY behavior\n  - Reviewing local cost usage scanning\n---\n\n# Codex provider\n\nCodex has four usage data paths (OAuth API, web dashboard, CLI RPC, CLI PTY) plus a local cost-usage scanner.\nThe OAuth API is the default app source when credentials are available; web access is optional for dashboard extras.\n\n## Data sources + fallback order\n\n### App default selection (debug menu disabled)\n1) OAuth API (auth.json credentials).\n2) CLI RPC, with CLI PTY fallback when needed.\n3) If OpenAI cookies are enabled (Automatic or Manual), dashboard extras load in parallel and the source label becomes\n   `primary + openai-web`.\n\nUsage source picker:\n- Preferences → Providers → Codex → Usage source (Auto/OAuth/CLI).\n\n### CLI default selection (`--source auto`)\n1) OpenAI web dashboard (when available).\n2) Codex CLI RPC, with CLI PTY fallback when needed.\n\n### OAuth API (preferred for the app)\n- Reads OAuth tokens from `~/.codex/auth.json` (or `$CODEX_HOME/auth.json`).\n- Refreshes access tokens when `last_refresh` is older than 8 days.\n- Calls `GET https://chatgpt.com/backend-api/wham/usage` (default) with `Authorization: Bearer <token>`.\n\n### OpenAI web dashboard (optional)\n- Preferences → Providers → Codex → OpenAI cookies (Automatic or Manual).\n- URL: `https://chatgpt.com/codex/settings/usage`.\n- Uses an off-screen `WKWebView` with a per-account `WKWebsiteDataStore`.\n  - Store key: deterministic UUID from the normalized email.\n- WebKit store can hold multiple accounts concurrently.\n- Cookie import (Automatic mode, when WebKit store has no matching session or login required):\n  1) Safari: `~/Library/Cookies/Cookies.binarycookies`\n  2) Chrome/Chromium forks: `~/Library/Application Support/Google/Chrome/*/Cookies`\n  3) Firefox: `~/Library/Application Support/Firefox/Profiles/*/cookies.sqlite`\n  - Domains loaded: `chatgpt.com`, `openai.com`.\n  - No cookie-name filter; we import all matching domain cookies.\n- Cached cookies: Keychain cache `com.steipete.codexbar.cache` (account `cookie.codex`, source + timestamp).\n  Reused before re-importing from browsers.\n- Manual cookie header:\n  - Paste the `Cookie:` header from a `chatgpt.com` request in Preferences → Providers → Codex.\n  - Used when OpenAI cookies are set to Manual.\n- Account match:\n  - Signed-in email extracted from `client-bootstrap` JSON in HTML (or `__NEXT_DATA__`).\n  - If Codex email is known and does not match, the web path is rejected.\n- Web scrape payload (via `OpenAIDashboardScrapeScript` + `OpenAIDashboardParser`):\n  - Rate limits (5h + weekly) parsed from body text.\n  - Credits remaining parsed from body text.\n  - Code review remaining (%).\n  - Usage breakdown chart (Recharts bar data + legend colors).\n  - Credits usage history table rows.\n  - Credits purchase URL (best-effort).\n- Errors surfaced:\n  - Login required or Cloudflare interstitial.\n\n### Codex CLI RPC (default CLI fallback)\n- Launches local RPC server: `codex -s read-only -a untrusted app-server`.\n- JSON-RPC over stdin/stdout:\n  - `initialize` (client name/version)\n  - `account/read`\n  - `account/rateLimits/read`\n- Provides:\n  - Usage windows (primary + secondary) with reset timestamps.\n  - Credits snapshot (balance, hasCredits, unlimited).\n  - Account identity (email + plan type) when available.\n\n### Codex CLI PTY fallback (`/status`)\n- Runs `codex` in a PTY via `TTYCommandRunner`.\n- Default behavior: exit after each probe; Debug → \"Keep CLI sessions alive\" keeps it running between probes.\n- Sends `/status`, parses the rendered screen:\n  - `Credits:` line\n  - `5h limit` line → percent + reset text\n  - `Weekly limit` line → percent + reset text\n- Retry once with a larger terminal size on parse failure (short retry window).\n- Do not retry on timeout; timed-out probes fail fast and wait for the next refresh cycle.\n- Detects update prompts and surfaces a \"CLI update needed\" error.\n\n## Account identity resolution (for web matching)\n1) Latest Codex usage snapshot (from RPC, if available).\n2) `~/.codex/auth.json` (JWT claims: email + plan).\n3) OpenAI dashboard signed-in email (cached).\n4) Last imported browser cookie email (cached).\n\n## Credits\n- Web dashboard fills credits only when OAuth/CLI do not provide them.\n- CLI RPC: `account/rateLimits/read` → credits balance.\n- CLI PTY fallback: parse `Credits:` from `/status`.\n\n## Cost usage (local log scan)\n- Source files:\n  - `~/.codex/sessions/YYYY/MM/DD/*.jsonl`\n  - `~/.codex/archived_sessions/*.jsonl` (flat; date inferred from filename when present)\n  - Or `$CODEX_HOME/sessions/...` + `$CODEX_HOME/archived_sessions/...` if `CODEX_HOME` is set.\n- Scanner:\n  - Parses `event_msg` token_count entries and `turn_context` model markers.\n  - Computes input/cached/output token deltas and per-model cost.\n- Cache:\n  - `~/Library/Caches/CodexBar/cost-usage/codex-v1.json`\n- Window: last 30 days (rolling), with a 60s minimum refresh interval.\n\n## Key files\n- Web: `Sources/CodexBarCore/OpenAIWeb/*`\n- CLI RPC + PTY: `Sources/CodexBarCore/UsageFetcher.swift`,\n  `Sources/CodexBarCore/Providers/Codex/CodexStatusProbe.swift`\n- Cost usage: `Sources/CodexBarCore/CostUsageFetcher.swift`,\n  `Sources/CodexBarCore/Vendored/CostUsage/*`\n"
  },
  {
    "path": "docs/configuration.md",
    "content": "---\nsummary: \"CodexBar config file layout for CLI + app settings.\"\nread_when:\n  - \"Editing the CodexBar config file or moving settings off Keychain.\"\n  - \"Adding new provider settings fields or defaults.\"\n  - \"Explaining CLI/app configuration and security.\"\n---\n\n# Configuration\n\nCodexBar reads a single JSON config file for CLI and app settings.\nSecrets (API keys, cookies, tokens) live here; Keychain is not used.\n\n## Location\n- `~/.codexbar/config.json`\n- The directory is created if missing.\n- Permissions are forced to `0600` on macOS and Linux.\n\n## Root shape\n```json\n{\n  \"version\": 1,\n  \"providers\": [\n    {\n      \"id\": \"codex\",\n      \"enabled\": true,\n      \"source\": \"auto\",\n      \"cookieSource\": \"auto\",\n      \"cookieHeader\": null,\n      \"apiKey\": null,\n      \"region\": null,\n      \"workspaceID\": null,\n      \"tokenAccounts\": null\n    }\n  ]\n}\n```\n\n## Provider fields\nAll provider fields are optional unless noted.\n\n- `id` (required): provider identifier.\n- `enabled`: enable/disable provider (defaults to provider default).\n- `source`: preferred source mode.\n  - `auto|web|cli|oauth|api`\n  - `auto` uses provider-specific fallback order (see `docs/providers.md`).\n  - `api` uses provider API key flow (when supported).\n- `apiKey`: raw API token for providers that support direct API usage.\n- `cookieSource`: cookie selection policy.\n  - `auto` (browser import), `manual` (use `cookieHeader`), `off` (disable cookies)\n- `cookieHeader`: raw cookie header value (e.g. `key=value; other=...`).\n- `region`: provider-specific region (e.g. `zai`, `minimax`).\n- `workspaceID`: provider-specific workspace ID (e.g. `opencode`).\n- `tokenAccounts`: multi-account tokens for a provider.\n\n### tokenAccounts\n```json\n{\n  \"version\": 1,\n  \"activeIndex\": 0,\n  \"accounts\": [\n    {\n      \"id\": \"00000000-0000-0000-0000-000000000000\",\n      \"label\": \"user@example.com\",\n      \"token\": \"sk-...\",\n      \"addedAt\": 1735123456,\n      \"lastUsed\": 1735220000\n    }\n  ]\n}\n```\n\n## Provider IDs\nCurrent IDs (see `Sources/CodexBarCore/Providers/Providers.swift`):\n`codex`, `claude`, `cursor`, `opencode`, `factory`, `gemini`, `antigravity`, `copilot`, `zai`, `minimax`, `kimi`, `kilo`, `kiro`, `vertexai`, `augment`, `jetbrains`, `kimik2`, `amp`, `ollama`, `synthetic`, `warp`, `openrouter`.\n\n## Ordering\nThe order of `providers` controls display/order in the app and CLI. Reorder the array to change ordering.\n\n## Notes\n- Fields not relevant to a provider are ignored.\n- Omitted providers are appended with defaults during normalization.\n- Keep the file private; it contains secrets.\n- Validate the file with `codexbar config validate` (JSON output available with `--format json`).\n"
  },
  {
    "path": "docs/copilot.md",
    "content": "---\nsummary: \"Copilot provider data sources: GitHub device flow + Copilot internal usage API.\"\nread_when:\n  - Debugging Copilot login or usage parsing\n  - Updating GitHub OAuth device flow behavior\n---\n\n# Copilot provider\n\nCopilot uses GitHub OAuth device flow and the Copilot internal usage API. No browser cookies.\n\n## Data sources + fallback order\n\n1) **GitHub OAuth device flow** (user initiated)\n   - Device code request:\n     - `POST https://github.com/login/device/code`\n   - Token polling:\n     - `POST https://github.com/login/oauth/access_token`\n   - Scope: `read:user`.\n   - Token stored in config:\n     - `~/.codexbar/config.json` → `providers[].apiKey` for `copilot`\n\n2) **Usage fetch**\n   - `GET https://api.github.com/copilot_internal/user`\n   - Headers:\n     - `Authorization: token <github_oauth_token>`\n     - `Accept: application/json`\n     - `Editor-Version: vscode/1.96.2`\n     - `Editor-Plugin-Version: copilot-chat/0.26.7`\n     - `User-Agent: GitHubCopilotChat/0.26.7`\n     - `X-Github-Api-Version: 2025-04-01`\n\n## Snapshot mapping\n- Primary: `quotaSnapshots.premiumInteractions` percent remaining → used percent.\n- Secondary: `quotaSnapshots.chat` percent remaining → used percent.\n- Reset dates are not provided by the API.\n- Plan label from `copilotPlan`.\n\n## Key files\n- `Sources/CodexBarCore/Providers/Copilot/CopilotUsageFetcher.swift`\n- `Sources/CodexBarCore/Providers/Copilot/CopilotDeviceFlow.swift`\n- `Sources/CodexBar/Providers/Copilot/CopilotLoginFlow.swift`\n- `Sources/CodexBar/CopilotTokenStore.swift` (legacy migration helper)\n"
  },
  {
    "path": "docs/cursor.md",
    "content": "---\nsummary: \"Cursor provider data sources: browser cookies or stored session; usage + billing via cursor.com APIs.\"\nread_when:\n  - Debugging Cursor usage parsing\n  - Updating Cursor cookie import or session storage\n  - Adjusting Cursor provider UI/menu behavior\n---\n\n# Cursor provider\n\nCursor is web-only. Usage is fetched via browser cookies or a stored WebKit session.\n\n## Data sources + fallback order\n\n1) **Cached cookie header** (preferred)\n   - Stored after successful browser import.\n   - Keychain cache: `com.steipete.codexbar.cache` (account `cookie.cursor`).\n\n2) **Browser cookie import**\n   - Cookie order from provider metadata (default: Safari → Chrome → Firefox).\n   - Domain filters: `cursor.com`, `cursor.sh`.\n   - Cookie names required (any one counts):\n     - `WorkosCursorSessionToken`\n     - `__Secure-next-auth.session-token`\n     - `next-auth.session-token`\n\n3) **Stored session cookies** (fallback)\n   - Captured by the \"Add Account\" WebKit login flow.\n   - Login teardown uses `WebKitTeardown` to avoid Intel WebKit crashes.\n   - Stored at: `~/Library/Application Support/CodexBar/cursor-session.json`.\n\nManual option:\n- Preferences → Providers → Cursor → Cookie source → Manual.\n- Paste the `Cookie:` header from a cursor.com request.\n\n## API endpoints\n- `GET https://cursor.com/api/usage-summary`\n  - Plan usage (included), on-demand usage, billing cycle window.\n- `GET https://cursor.com/api/auth/me`\n  - User email + name.\n- `GET https://cursor.com/api/usage?user=ID`\n  - Legacy request-based plan usage (request counts + limits).\n\n## Cookie file paths\n- Safari: `~/Library/Cookies/Cookies.binarycookies`\n- Chrome/Chromium forks: `~/Library/Application Support/Google/Chrome/*/Cookies`\n- Firefox: `~/Library/Application Support/Firefox/Profiles/*/cookies.sqlite`\n\n## Snapshot mapping\n- Primary: plan usage percent (included plan).\n- Secondary: on-demand usage percent (individual usage).\n- Provider cost: on-demand usage USD (limit when known).\n- Reset: billing cycle end date.\n\n## Key files\n- `Sources/CodexBarCore/Providers/Cursor/CursorStatusProbe.swift`\n- `Sources/CodexBar/CursorLoginRunner.swift` (login flow)\n"
  },
  {
    "path": "docs/factory.md",
    "content": "---\nsummary: \"Factory (Droid) provider data sources: browser cookies, WorkOS tokens, and Factory APIs.\"\nread_when:\n  - Debugging Factory/Droid usage fetch\n  - Updating Factory cookie or WorkOS token handling\n  - Adjusting Factory provider UI/menu behavior\n---\n\n# Factory (Droid) provider\n\nFactory (displayed as \"Droid\") is web-based. We authenticate via cookies or WorkOS tokens and call Factory APIs.\n\n## Data sources + fallback order\n\nFetch attempts run in this exact order:\n1) **Cached cookie header** (Keychain cache `com.steipete.codexbar.cache`, account `cookie.factory`).\n2) **Stored session** (`~/Library/Application Support/CodexBar/factory-session.json`).\n3) **Stored bearer token** (same session file).\n4) **Stored WorkOS refresh token** (same session file).\n5) **Local storage WorkOS tokens** (Safari + Chrome/Chromium/Arc leveldb).\n6) **Browser cookies (Safari only)** for Factory domains.\n7) **WorkOS cookies (Safari)** to mint tokens.\n8) **Browser cookies (Chrome, Firefox)** for Factory domains.\n9) **WorkOS cookies (Chrome, Firefox)** to mint tokens.\n\nIf a step succeeds, we cache cookies/tokens back into the session store.\n\nManual option:\n- Preferences → Providers → Droid → Cookie source → Manual.\n- Paste the `Cookie:` header from app.factory.ai.\n\n## Cookie import\n- Cookie domains: `factory.ai`, `app.factory.ai`, `auth.factory.ai`.\n- Cookie names considered a session:\n  - `wos-session`\n  - `__Secure-next-auth.session-token`\n  - `next-auth.session-token`\n  - `__Secure-authjs.session-token`\n  - `__Host-authjs.csrf-token`\n  - `authjs.session-token`\n  - `session`\n  - `access-token`\n- Stale-token retry filters:\n  - `access-token`, `__recent_auth`.\n\n## Base URL selection\n- Candidates are tried in order (deduped):\n  - `https://auth.factory.ai`\n  - `https://api.factory.ai`\n  - `https://app.factory.ai`\n  - `baseURL` (default `https://app.factory.ai`)\n- Cookie domains influence candidate ordering (auth domain first if present).\n\n## Factory API endpoints\nAll requests set:\n- `Accept: application/json`\n- `Content-Type: application/json`\n- `Origin: https://app.factory.ai`\n- `Referer: https://app.factory.ai/`\n- `x-factory-client: web-app`\n- `Authorization: Bearer <token>` when a bearer token is available.\n- `Cookie: <session cookies>` when cookies are available.\n\nEndpoints:\n- `GET <baseURL>/api/app/auth/me`\n  - Returns org + subscription metadata + feature flags.\n- `POST <baseURL>/api/organization/subscription/usage`\n  - Body: `{ \"useCache\": true, \"userId\": \"<id?>\" }`\n  - Returns Standard + Premium token usage and billing window.\n\n## WorkOS token minting\n- Endpoint:\n  - `POST https://api.workos.com/user_management/authenticate`\n- Body:\n  - `client_id`: one of\n    - `client_01HXRMBQ9BJ3E7QSTQ9X2PHVB7`\n    - `client_01HNM792M5G5G1A2THWPXKFMXB`\n  - `grant_type`: `refresh_token`\n  - `refresh_token`: from local storage or session store\n  - Optional: `organization_id`\n  - When using cookies: `useCookie: true` + `Cookie: <workos.com cookies>`\n\n## Local storage WorkOS token extraction\n- Safari:\n  - Root: `~/Library/Containers/com.apple.Safari/Data/Library/WebKit/WebsiteData/Default`\n  - Finds `origin` files containing `app.factory.ai` or `auth.factory.ai`, then reads\n    `LocalStorage/localstorage.sqlite3`.\n- Chrome/Chromium/Arc/Helium:\n  - Roots under `~/Library/Application Support/<Browser>/User Data/<Profile>/Local Storage/leveldb`.\n  - Helium uses `~/Library/Application Support/net.imput.helium/<Profile>/Local Storage/leveldb` (no `User Data`).\n  - Scans LevelDB files for `workos:refresh-token` and `workos:access-token`.\n- Parsed tokens:\n  - `workos:refresh-token` (required)\n  - `workos:access-token` (optional)\n  - Organization ID parsed from JWT when available.\n\n## Session storage\n- File: `~/Library/Application Support/CodexBar/factory-session.json`\n- Stores cookies + bearer token + WorkOS refresh token.\n\n## Snapshot mapping\n- Primary: Standard usage ratio.\n- Secondary: Premium usage ratio.\n- Reset: billing period end date.\n- Plan/tier + org name from auth response.\n\n## Key files\n- `Sources/CodexBarCore/Providers/Factory/FactoryStatusProbe.swift`\n- `Sources/CodexBarCore/Providers/Factory/FactoryLocalStorageImporter.swift`\n"
  },
  {
    "path": "docs/gemini.md",
    "content": "---\nsummary: \"Gemini provider data sources: OAuth-backed quota APIs, token refresh, and tier detection.\"\nread_when:\n  - Debugging Gemini quota fetch or auth issues\n  - Updating Gemini CLI OAuth integration\n  - Adjusting tier detection or model mapping\n---\n\n# Gemini provider\n\nGemini uses the Gemini CLI OAuth credentials and private quota APIs. No browser cookies.\n\n## Data sources + fallback order\n\n1) **OAuth-backed quota API** (only path used in `fetch()`)\n   - Reads auth type from `~/.gemini/settings.json`.\n   - Supported: `oauth-personal` (or unknown → try OAuth creds).\n   - Unsupported: `api-key`, `vertex-ai` (hard error).\n\n2) **Legacy CLI parsing** (parser exists but not used in current fetch path)\n   - `GeminiStatusProbe.parse(text:)` can parse `/stats` output.\n\n## OAuth credentials\n- File: `~/.gemini/oauth_creds.json`.\n- Required fields: `access_token`, `refresh_token` (optional), `id_token`, `expiry_date`.\n- If access token is expired, we refresh via Google OAuth using client ID/secret extracted\n  from the Gemini CLI install (see below).\n\n## OAuth client ID/secret extraction\n- We locate the installed `gemini` binary, then search for:\n  - Homebrew nested path:\n    - `.../libexec/lib/node_modules/@google/gemini-cli/node_modules/@google/gemini-cli-core/dist/src/code_assist/oauth2.js`\n  - Bun/npm sibling path:\n    - `.../node_modules/@google/gemini-cli-core/dist/src/code_assist/oauth2.js`\n- Regex extraction:\n  - `OAUTH_CLIENT_ID` and `OAUTH_CLIENT_SECRET` from `oauth2.js`.\n\n## API endpoints\n- Quota:\n  - `POST https://cloudcode-pa.googleapis.com/v1internal:retrieveUserQuota`\n  - Body: `{ \"project\": \"<projectId>\" }` (or `{}` if unknown)\n  - Header: `Authorization: Bearer <access_token>`\n- Project discovery (quota project ID):\n  - Primary: `cloudaicompanionProject` from `loadCodeAssist`.\n  - Fallback: `GET https://cloudresourcemanager.googleapis.com/v1/projects`\n    - Picks `gen-lang-client*` or label `generative-language`.\n- Tier detection:\n  - `POST https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist`\n  - Body: `{ \"metadata\": { \"ideType\": \"GEMINI_CLI\", \"pluginType\": \"GEMINI\" } }`\n- Token refresh:\n  - `POST https://oauth2.googleapis.com/token`\n  - Form body: `client_id`, `client_secret`, `refresh_token`, `grant_type=refresh_token`.\n\n## Parsing + mapping\n- Quota buckets:\n  - `remainingFraction`, `resetTime`, `modelId`.\n  - For each model, lowest `remainingFraction` wins.\n  - `percentLeft = remainingFraction * 100`.\n- Reset:\n  - `resetTime` parsed as ISO-8601, formatted as \"Resets in Xh Ym\".\n- UI mapping:\n  - Primary: Pro models (lowest percent left).\n  - Secondary: Flash models (lowest percent left).\n\n## Plan detection\n- Tier from `loadCodeAssist`:\n  - `standard-tier` → \"Paid\"\n  - `free-tier` + `hd` claim → \"Workspace\"\n  - `free-tier` → \"Free\"\n  - `legacy-tier` → \"Legacy\"\n- Email from `id_token` JWT claims.\n\n## Key files\n- `Sources/CodexBarCore/Providers/Gemini/GeminiStatusProbe.swift`\n"
  },
  {
    "path": "docs/icon.md",
    "content": "---\nsummary: \"Convert macOS .icon bundles to CodexBar .icns via Scripts/build_icon.sh and ictool.\"\nread_when:\n  - Updating the CodexBar app icon or asset pipeline\n  - Preparing release builds that need a refreshed icns\n---\n\n# Icon pipeline (macOS .icon → .icns without Xcodebuild)\n\nWe use the new macOS 26 “glass” `.icon` bundle from Icon Composer/IconStudio and convert it to `.icns` via Xcode’s hidden CLI (ictool/icontool), without an Xcode project.\n\n## Script\n`Scripts/build_icon.sh ICON.icon CodexBar [outdir]`\n\nWhat it does:\n1) Finds `ictool` (or `icontool`) in `/Applications/Xcode.app/Contents/Applications/Icon Composer.app/Contents/Executables/`.\n2) Renders the macOS Default appearance of the `.icon` to an 824×824 PNG (inner art, glass applied).\n3) Pads to 1024×1024 with transparency (restores Tahoe squircle margin, avoids “white plate”).\n4) Downscales to all required sizes into an `.iconset`.\n5) Runs `iconutil -c icns` → `Icon.icns`.\n\nRequirements:\n- Xcode 26+ installed (IC tool lives inside the Xcode bundle).\n- `sips` and `iconutil` (system tools).\n\nUsage:\n```bash\n./Scripts/build_icon.sh Icon.icon CodexBar\n```\nOutputs `Icon.icns` at repo root.\n\nWhy this approach:\n- Naive `sips`/`iconutil` from raw PNGs often leaves a white/grey plate because the inner art is full-bleed. The ictool render + transparent padding matches Xcode’s asset-pipeline output.\n\nNotes:\n- If Xcode is in a nonstandard location, set `XCODE_APP=/path/to/Xcode.app` before running.\n- Script is CI-friendly; no Xcode project needed.\n"
  },
  {
    "path": "docs/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <title>CodexBar</title>\n    <meta\n      name=\"description\"\n      content=\"Tiny macOS menu bar app that keeps your Codex, Claude Code, Cursor, Gemini, Antigravity, Droid (Factory), Copilot, and z.ai limits visible.\"\n    />\n    <meta name=\"theme-color\" content=\"#0b1016\" />\n    <link rel=\"canonical\" href=\"https://codexbar.app/\" />\n\n    <meta property=\"og:title\" content=\"CodexBar\" />\n    <meta\n      property=\"og:description\"\n      content=\"Tiny macOS menu bar app that keeps your Codex, Claude Code, Cursor, Gemini, Antigravity, Droid (Factory), Copilot, and z.ai limits visible.\"\n    />\n    <meta property=\"og:type\" content=\"website\" />\n    <meta property=\"og:url\" content=\"https://codexbar.app/\" />\n    <meta property=\"og:image\" content=\"https://codexbar.app/codexbar.png\" />\n    <meta name=\"twitter:card\" content=\"summary_large_image\" />\n\n    <link rel=\"icon\" href=\"./icon.png\" type=\"image/png\" />\n    <link rel=\"apple-touch-icon\" href=\"./icon.png\" />\n    <link rel=\"stylesheet\" href=\"./site.css?v=a1b2c3d\" />\n  </head>\n  <body>\n    <main class=\"wrap\">\n      <header class=\"top\" data-animate=\"1\">\n        <a class=\"brand\" href=\"https://github.com/steipete/CodexBar\" aria-label=\"CodexBar on GitHub\">\n          <span class=\"name\">\n            <h1 class=\"title\">CodexBar</h1>\n            <p class=\"tagline\">Codex, Claude Code, Cursor, Gemini, Antigravity, Droid, Copilot, z.ai limits.</p>\n          </span>\n        </a>\n        <nav class=\"links\" aria-label=\"Links\">\n          <a class=\"btn primary\" href=\"https://github.com/steipete/CodexBar/releases/latest\">Download</a>\n          <a class=\"btn ghost\" href=\"https://github.com/steipete/CodexBar\">Source</a>\n        </nav>\n      </header>\n\n      <section class=\"hero\" aria-label=\"Overview\">\n        <div class=\"copy\" data-animate=\"2\">\n          <div class=\"badges\" aria-label=\"Highlights\">\n            <span class=\"badge\">macOS 14+</span>\n            <span class=\"badge\">Apple Silicon + Intel</span>\n            <span class=\"badge\">Free &amp; open source</span>\n            <span class=\"badge\">No passwords stored</span>\n          </div>\n          <div class=\"hero-heading\">\n            <img class=\"hero-icon\" src=\"./icon.png\" alt=\"\" aria-hidden=\"true\" width=\"40\" height=\"40\" />\n            <div>\n              <h2>Stay ahead of resets.</h2>\n              <p>\n                CodexBar keeps session + weekly limits (and credits) in the menu bar, so you know when you're safe to ship.\n              </p>\n            </div>\n          </div>\n          <p class=\"requirement\">Requirements: macOS 14+ (Apple Silicon + Intel).</p>\n          <div class=\"cta\">\n            <a class=\"btn primary\" href=\"https://github.com/steipete/CodexBar/releases/latest\">Download latest</a>\n            <a class=\"btn\" href=\"https://github.com/steipete/CodexBar\">View on GitHub</a>\n          </div>\n          <p class=\"brew-line\">\n            Homebrew: <code id=\"brew-cmd\">brew install --cask steipete/tap/codexbar</code>\n            <button class=\"copy-btn\" onclick=\"copyBrew()\" aria-label=\"Copy to clipboard\" title=\"Copy to clipboard\">\n              <svg class=\"icon-copy\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n                <rect x=\"5\" y=\"5\" width=\"9\" height=\"9\" rx=\"1.5\"/>\n                <path d=\"M5 11H3.5A1.5 1.5 0 012 9.5v-7A1.5 1.5 0 013.5 1h7A1.5 1.5 0 0112 2.5V5\"/>\n              </svg>\n              <svg class=\"icon-check\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n                <polyline points=\"3 8 6 11 13 4\"/>\n              </svg>\n            </button>\n          </p>\n          <p class=\"brew-line\">Linuxbrew (CLI only): <code>brew install steipete/tap/codexbar</code></p>\n          <p class=\"note\">\n            GitHub Releases builds update via Sparkle. Homebrew installs update via <code>brew upgrade</code>.\n          </p>\n          <script>\n            function copyBrew() {\n              const cmd = document.getElementById('brew-cmd').textContent;\n              navigator.clipboard.writeText(cmd).then(() => {\n                document.querySelector('.copy-btn').classList.add('copied');\n                setTimeout(() => document.querySelector('.copy-btn').classList.remove('copied'), 1500);\n              });\n            }\n          </script>\n          <ul class=\"bullets\" aria-label=\"What it does\">\n            <li>One status item per provider (Codex, Claude, Cursor, Gemini, Antigravity, Droid, Copilot, z.ai, Kilo).</li>\n            <li>Session + weekly bars with reset times and optional credits.</li>\n            <li>Web-backed limits via browser cookies (Codex, Claude, Cursor, Droid).</li>\n            <li>Two-bar icon: session on top, weekly on bottom, dimmed on errors.</li>\n            <li>Minimal UI, no Dock icon.</li>\n          </ul>\n        </div>\n        <figure class=\"shot hero-shot\" data-animate=\"3\">\n          <img src=\"./codexbar.png\" alt=\"CodexBar menu bar popover showing session and weekly usage.\" />\n          <figcaption>Menu card shows session + weekly usage, resets, and credits when available.</figcaption>\n        </figure>\n      </section>\n\n      <section class=\"details\" aria-label=\"Details\" data-animate=\"4\">\n        <div class=\"card\">\n          <h3>Install &amp; updates</h3>\n          <p>\n            Download from GitHub Releases or install via Homebrew. Sparkle updates are enabled on GitHub builds only.\n          </p>\n        </div>\n        <div class=\"card\">\n          <h3>Providers</h3>\n          <p>\n            Codex · Claude Code · Cursor · Gemini · Antigravity · Droid · Copilot · z.ai · Kilo<br />\n            Open to new providers — see the <a href=\"https://github.com/steipete/CodexBar/blob/main/docs/provider.md\">provider guide</a>.\n          </p>\n        </div>\n        <div class=\"card\">\n          <h3>Privacy</h3>\n          <p>\n            Reuses existing browser cookies (Safari/Chrome/Firefox) for dashboard access — no passwords stored. When cookies\n            are missing, CodexBar falls back to local CLI output. Audit notes in\n            <a href=\"https://github.com/steipete/CodexBar/issues/12\">issue #12</a>.\n          </p>\n        </div>\n      </section>\n\n      <section class=\"details\" aria-label=\"Features\" data-animate=\"5\">\n        <div class=\"card\">\n          <h3>Usage tracking</h3>\n          <p>\n            Session + weekly meters with reset countdowns, plus optional credits and code review limits.\n          </p>\n        </div>\n        <div class=\"card\">\n          <h3>Status + widgets</h3>\n          <p>\n            Provider status polling with incident badges, plus a WidgetKit snapshot that mirrors the menu card.\n          </p>\n        </div>\n        <div class=\"card\">\n          <h3>CLI + automation</h3>\n          <p>\n            Bundled <code>codexbar</code> CLI for scripts and CI, with Linux CLI builds on GitHub Releases.\n          </p>\n        </div>\n      </section>\n\n      <section class=\"details permissions\" aria-label=\"Permissions\" data-animate=\"6\">\n        <div class=\"card\">\n          <h3>macOS permissions</h3>\n          <p>\n            Full Disk Access only for Safari cookies, Keychain access for tokens, and Files &amp; Folders prompts when\n            provider CLIs access project directories.\n          </p>\n        </div>\n      </section>\n\n      <section class=\"links-row\" aria-label=\"Docs links\" data-animate=\"7\">\n        <a class=\"link-pill\" href=\"https://github.com/steipete/CodexBar/blob/main/docs/providers.md\">Docs</a>\n        <a class=\"link-pill\" href=\"https://github.com/steipete/CodexBar/blob/main/docs/cli.md\">CLI</a>\n        <a class=\"link-pill\" href=\"https://github.com/steipete/CodexBar/blob/main/docs/provider.md\">Provider guide</a>\n      </section>\n\n      <footer class=\"foot\" data-animate=\"8\">\n        <p>\n          Built by <a href=\"https://steipete.me\">Peter Steinberger</a> •\n          <a href=\"https://github.com/steipete/CodexBar/blob/main/CHANGELOG.md\">Changelog</a> •\n          <a href=\"https://github.com/steipete/CodexBar/blob/main/LICENSE\">MIT</a> •\n          <a href=\"https://codexbar.app\">codexbar.app</a> •\n          <a href=\"https://trimmy.app\">trimmy.app</a>\n        </p>\n      </footer>\n    </main>\n  </body>\n</html>\n"
  },
  {
    "path": "docs/jetbrains.md",
    "content": "---\nsummary: \"JetBrains AI provider notes: local XML quota parsing, IDE auto-detection, and UI mapping.\"\nread_when:\n  - Adding or modifying the JetBrains AI provider\n  - Debugging JetBrains quota file parsing or IDE detection\n  - Adjusting JetBrains menu labels or settings\n---\n\n# JetBrains AI provider\n\nJetBrains AI is a local-only provider. We read quota information directly from the IDE's configuration files.\n\n## Data sources + fallback order\n\n1) **IDE auto-detection**\n   - macOS: `~/Library/Application Support/JetBrains/`\n   - macOS (Android Studio): `~/Library/Application Support/Google/`\n   - Linux: `~/.config/JetBrains/`\n   - Linux (Android Studio): `~/.config/Google/`\n   - Supported IDEs: IntelliJ IDEA, PyCharm, WebStorm, GoLand, CLion, DataGrip, RubyMine, Rider, PhpStorm, RustRover, Android Studio, Fleet, Aqua, DataSpell\n   - Selection: most recently modified `AIAssistantQuotaManager2.xml`\n\n2) **Quota file parsing**\n   - Path: `<IDE_BASE>/options/AIAssistantQuotaManager2.xml` (macOS/Linux)\n   - Format: XML with HTML-encoded JSON attributes\n\n## XML structure\n\n- `quotaInfo` attribute (JSON):\n  - `type`: quota type (e.g., \"Available\")\n  - `current`: tokens used\n  - `maximum`: total tokens\n  - `tariffQuota.available`: remaining tokens\n  - `until`: subscription end date\n- `nextRefill` attribute (JSON):\n  - `type`: refill type (e.g., \"Known\")\n  - `next`: next refill date (ISO-8601)\n  - `tariff.amount`: refill amount\n  - `tariff.duration`: refill period (e.g., \"PT720H\")\n\n## Parsing and mapping\n\n- Usage calculation: `tariffQuota.available / maximum * 100` for remaining percent\n- Reset date: from `nextRefill.next`, not `quotaInfo.until`\n- HTML entity decoding: `&#10;` → newline, `&quot;` → quote\n\n## UI mapping\n\n- Provider metadata:\n  - Display: `JetBrains AI`\n  - Label: `Current` (primary only)\n- Identity: detected IDE name + version (e.g., \"IntelliJ IDEA 2025.3\")\n- Status badge: none (no status page integration)\n\n## Settings\n\n- IDE Picker: auto-detected IDEs list, or \"Auto-detect\" (default)\n- Custom Path: manual base path override (for advanced users)\n\n## Constraints\n\n- Requires JetBrains IDE with AI Assistant enabled\n- XML file only exists after AI Assistant usage\n- Internal file format; may change between IDE versions\n\n## Key files\n\n- `Sources/CodexBarCore/Providers/JetBrains/JetBrainsStatusProbe.swift`\n- `Sources/CodexBarCore/Providers/JetBrains/JetBrainsIDEDetector.swift`\n- `Sources/CodexBar/Providers/JetBrains/JetBrainsProviderImplementation.swift`\n"
  },
  {
    "path": "docs/kilo.md",
    "content": "---\nsummary: \"Kilo provider data sources: app.kilo.ai API token and CLI auth-file fallback.\"\nread_when:\n  - Adding or modifying the Kilo provider\n  - Adjusting Kilo source-mode fallback behavior\n  - Troubleshooting Kilo credentials/auth sessions\n---\n\n# Kilo provider\n\nKilo supports API and CLI-backed auth. Source mode can be `auto`, `api`, or `cli`.\n\n## Data sources + fallback order\n1. API (`api`)\n   - Token from `~/.codexbar/config.json` (`providers[].apiKey` for `kilo`) or `KILO_API_KEY`.\n   - Calls `https://app.kilo.ai/api/trpc`.\n2. CLI session (`cli`)\n   - Reads `~/.local/share/kilo/auth.json` and uses `kilo.access`.\n   - Requires a valid CLI login (`kilo login`).\n3. Auto (`auto`)\n   - Tries API first.\n   - Falls back to CLI only when API credentials are missing or unauthorized (401/403).\n\n## Settings\n- Preferences -> Providers -> Kilo:\n  - Usage source: `Auto`, `API`, `CLI`\n  - API key: optional override for `KILO_API_KEY`\n- In auto mode, resolved CLI fetches can show a fallback note in menu and CLI output.\n\n## CLI output notes\n- Kilo text output splits identity into `Plan:` and `Activity:` lines.\n- Auto-mode failures include ordered fallback-attempt details in text mode.\n\n## Troubleshooting\n- Missing API token: set `KILO_API_KEY` or provider `apiKey`.\n- Missing CLI session file: run `kilo login` to create `~/.local/share/kilo/auth.json`.\n- Unauthorized API token (401/403): refresh `KILO_API_KEY` or rerun `kilo login`.\n"
  },
  {
    "path": "docs/kimi-k2.md",
    "content": "---\nsummary: \"Kimi K2 provider data sources: API key + credit endpoint.\"\nread_when:\n  - Adding or tweaking Kimi K2 usage parsing\n  - Updating API key handling or config migration\n  - Documenting new provider behavior\n---\n\n# Kimi K2 provider\n\nKimi K2 is API-only. Usage is reported by the credit counter behind `GET https://kimi-k2.ai/api/user/credits`,\nso CodexBar only needs a valid API key to pull your remaining balance and usage.\n\n## Data sources + fallback order\n\n1) **API key** stored in `~/.codexbar/config.json` or supplied via `KIMI_K2_API_KEY` / `KIMI_API_KEY` / `KIMI_KEY`.\n   CodexBar stores the key in config after you paste it in Preferences → Providers → Kimi K2.\n2) **Credit endpoint**\n   - `GET https://kimi-k2.ai/api/user/credits`\n   - Request headers: `Authorization: Bearer <api key>`, `Accept: application/json`\n   - Response headers may include `X-Credits-Remaining`.\n   - JSON payload contains total credits consumed, credits remaining, and optional usage metadata.\n     CodexBar scans common keys and falls back to the remaining header when JSON omits it.\n\n## Usage details\n\n- Credits are the billing unit; CodexBar computes used percent as `consumed / (consumed + remaining)`.\n- There is no explicit reset timestamp in the API, so the snapshot has no reset time.\n- Environment variables take precedence over config.\n\n## Key files\n\n- `Sources/CodexBarCore/Providers/KimiK2/KimiK2ProviderDescriptor.swift` (descriptor + fetch strategy)\n- `Sources/CodexBarCore/Providers/KimiK2/KimiK2UsageFetcher.swift` (HTTP client + parser)\n- `Sources/CodexBarCore/Providers/KimiK2/KimiK2SettingsReader.swift` (env var parsing)\n- `Sources/CodexBar/Providers/KimiK2/KimiK2ProviderImplementation.swift` (settings field + activation logic)\n- `Sources/CodexBar/KimiK2TokenStore.swift` (legacy migration helper)\n"
  },
  {
    "path": "docs/kimi.md",
    "content": "---\nsummary: \"Kimi provider notes: cookie auth, quotas, and rate-limit parsing.\"\nread_when:\n  - Adding or modifying the Kimi provider\n  - Debugging Kimi cookie import or usage parsing\n  - Adjusting Kimi menu labels or settings\n---\n\n# Kimi Provider\n\nTracks usage for [Kimi For Coding](https://www.kimi.com/code) in CodexBar.\n\n## Features\n\n- Displays weekly request quota (from membership tier)\n- Shows current 5-hour rate limit usage\n- Automatic and manual authentication methods\n- Automatic refresh countdown\n\n## Setup\n\nChoose one of two authentication methods:\n\n### Method 1: Automatic Browser Import (Recommended)\n\n**No setup needed!** If you're already logged in to Kimi in Arc, Chrome, Safari, Edge, Brave, or Chromium:\n\n1. Open CodexBar settings → Providers → Kimi\n2. Set \"Cookie source\" to \"Automatic\"\n3. Enable the Kimi provider toggle\n4. CodexBar will automatically find your session\n\n**Note**: Requires Full Disk Access to read browser cookies (System Settings → Privacy & Security → Full Disk Access → CodexBar).\n\n### Method 2: Manual Token Entry\n\nFor advanced users or when automatic import fails:\n\n1. Open CodexBar settings → Providers → Kimi\n2. Set \"Cookie source\" to \"Manual\"\n3. Visit `https://www.kimi.com/code/console` in your browser\n4. Open Developer Tools (F12 or Cmd+Option+I)\n5. Go to **Application** → **Cookies**\n6. Copy the `kimi-auth` cookie value (JWT token)\n7. Paste it into the \"Auth Token\" field in CodexBar\n\n### Method 3: Environment Variable\n\nAlternatively, set the `KIMI_AUTH_TOKEN` environment variable:\n\n```bash\nexport KIMI_AUTH_TOKEN=\"jwt-token-here\"\n```\n\n## Authentication Priority\n\nWhen multiple sources are available, CodexBar uses this order:\n\n1. Manual token (from Settings UI)\n2. Environment variable (`KIMI_AUTH_TOKEN`)\n3. Browser cookies (Arc → Chrome → Safari → Edge → Brave → Chromium)\n\n**Note**: Browser cookie import requires Full Disk Access permission.\n\n## API Details\n\n**Endpoint**: `POST https://www.kimi.com/apiv2/kimi.gateway.billing.v1.BillingService/GetUsages`\n\n**Authentication**: Bearer token (from `kimi-auth` cookie)\n\n**Response**:\n```json\n{\n  \"usages\": [{\n    \"scope\": \"FEATURE_CODING\",\n    \"detail\": {\n      \"limit\": \"2048\",\n      \"used\": \"214\",\n      \"remaining\": \"1834\",\n      \"resetTime\": \"2026-01-09T15:23:13.716839300Z\"\n    },\n    \"limits\": [{\n      \"window\": {\"duration\": 300, \"timeUnit\": \"TIME_UNIT_MINUTE\"},\n      \"detail\": {\n        \"limit\": \"200\",\n        \"used\": \"139\",\n        \"remaining\": \"61\",\n        \"resetTime\": \"2026-01-06T13:33:02.717479433Z\"\n      }\n    }]\n  }]\n}\n```\n\n## Membership Tiers\n\n| Tier | Price | Weekly Quota |\n|------|-------|--------------|\n| Andante | ¥49/month | 1,024 requests |\n| Moderato | ¥99/month | 2,048 requests |\n| Allegretto | ¥199/month | 7,168 requests |\n\nAll tiers have a rate limit of 200 requests per 5 hours.\n\n## Troubleshooting\n\n### \"Kimi auth token is missing\"\n- Ensure \"Cookie source\" is set correctly\n- If using Automatic mode, verify you're logged in to Kimi in your browser\n- Grant Full Disk Access permission if using browser cookies\n- Try Manual mode and paste your token directly\n\n### \"Kimi auth token is invalid or expired\"\n- Your token has expired. Paste a new token from your browser\n- If using Automatic mode, log in to Kimi again in your browser\n\n### \"No Kimi session cookies found\"\n- You're not logged in to Kimi in any supported browser\n- Grant Full Disk Access to CodexBar in System Settings\n\n### \"Failed to parse Kimi usage data\"\n- The API response format may have changed. Please report this issue.\n\n## Implementation\n\n- **Core files**: `Sources/CodexBarCore/Providers/Kimi/`\n- **UI files**: `Sources/CodexBar/Providers/Kimi/`\n- **Login flow**: `Sources/CodexBar/KimiLoginRunner.swift`\n- **Tests**: `Tests/CodexBarTests/KimiProviderTests.swift`\n"
  },
  {
    "path": "docs/kiro.md",
    "content": "---\nsummary: \"Kiro provider data sources: CLI-based usage via kiro-cli /usage command.\"\nread_when:\n  - Debugging Kiro usage parsing\n  - Updating kiro-cli command behavior\n  - Reviewing Kiro credit window mapping\n---\n\n# Kiro provider\n\nKiro uses the AWS `kiro-cli` tool to fetch usage data. No browser cookies or OAuth flow—authentication is handled by AWS Builder ID through the CLI.\n\n## Data sources\n\n1) **CLI command** (primary and only strategy)\n   - Command: `kiro-cli chat --no-interactive \"/usage\"`\n   - Timeout: 20 seconds (idle cutoff after ~10s of no output once the CLI starts responding).\n   - Requires `kiro-cli` installed and logged in via AWS Builder ID.\n   - Output is ANSI-decorated; CodexBar strips escape sequences before parsing.\n\n## Output format (example)\n\n```\n┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n┃                                                          | KIRO FREE      ┃\n┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫\n┃ Monthly credits:                                                          ┃\n┃ ████████████████████████████████████████████████████████ 100% (resets on 01/01) ┃\n┃                              (0.00 of 50 covered in plan)                 ┃\n┃ Bonus credits:                                                            ┃\n┃ 0.00/100 credits used, expires in 88 days                                 ┃\n┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n```\n\n## Snapshot mapping\n\n- **Primary window**: Monthly credits percentage (bar meter).\n  - `usedPercent`: extracted from `███...█ X%` pattern.\n  - `resetsAt`: parsed from `resets on MM/DD` (assumes current or next year).\n- **Secondary window**: Bonus credits (when present).\n  - Parsed from `Bonus credits: X.XX/Y credits used`.\n  - Expiry from `expires in N days`.\n- **Identity**:\n  - `accountOrganization`: plan name (e.g., \"KIRO FREE\").\n  - `loginMethod`: plan name (used for menu display).\n\n## Status\n\nKiro does not have a dedicated status page. The \"View Status\" link opens the AWS Health Dashboard:\n- `https://health.aws.amazon.com/health/status`\n\n## Key files\n\n- `Sources/CodexBarCore/Providers/Kiro/KiroProviderDescriptor.swift`\n- `Sources/CodexBarCore/Providers/Kiro/KiroStatusProbe.swift`\n- `Sources/CodexBar/Providers/Kiro/KiroProviderImplementation.swift`\n"
  },
  {
    "path": "docs/minimax.md",
    "content": "---\nsummary: \"MiniMax provider data sources: API token or browser cookies + coding plan remains API.\"\nread_when:\n  - Debugging MiniMax usage parsing\n  - Updating MiniMax cookie handling or coding plan scraping\n  - Adjusting MiniMax provider UI/menu behavior\n---\n\n# MiniMax provider\n\nMiniMax supports API tokens or web sessions. Usage is fetched from the Coding Plan remains API using\neither a Bearer API token or a session cookie header.\n\n## Data sources + fallback order\n\n1) **API token** (preferred)\n   - Set in Preferences → Providers → MiniMax (stored in `~/.codexbar/config.json`) or `MINIMAX_API_KEY`.\n   - When present, MiniMax uses the API token and ignores cookies entirely.\n\n2) **Cached cookie header** (automatic, only when no API token)\n   - Keychain cache: `com.steipete.codexbar.cache` (account `cookie.minimax`).\n\n3) **Browser cookie import** (automatic)\n   - Cookie order from provider metadata (default: Safari → Chrome → Firefox).\n   - Merges Chromium profile cookies across the primary + Network stores before attempting a request.\n   - Tries each browser source until the Coding Plan API accepts the cookies.\n   - Domain filters: `platform.minimax.io`, `minimax.io`.\n\n4) **Browser local storage access token** (Chromium-based)\n   - Reads `access_token` (and related tokens) from Chromium local storage (LevelDB) to authorize the remains API.\n   - If decoding fails, falls back to a text-entry scan for `minimax.io` keys/values and filters for MiniMax JWT claims.\n   - Used automatically; no UI field.\n   - Also extracts `GroupId` when present (appends query param).\n\n5) **Manual session cookie header** (optional override)\n   - Stored in `~/.codexbar/config.json` via Preferences → Providers → MiniMax (Cookie source → Manual).\n   - Accepts a raw `Cookie:` header or a full \"Copy as cURL\" string.\n   - When a cURL string is pasted, MiniMax extracts the cookie header plus `Authorization: Bearer …` and\n     `GroupId=…` for the remains API.\n   - CLI/runtime env: `MINIMAX_COOKIE` or `MINIMAX_COOKIE_HEADER`.\n\n## Endpoints\n- API token endpoint: `https://api.minimax.io/v1/coding_plan/remains`\n  - Requires `Authorization: Bearer <api_token>`.\n- Global host (cookies): `https://platform.minimax.io`\n- China mainland host: `https://platform.minimaxi.com`\n- `GET {host}/user-center/payment/coding-plan`\n  - HTML parse for \"Available usage\" and plan name.\n- `GET {host}/v1/api/openplatform/coding_plan/remains`\n  - Fallback when HTML parsing fails.\n  - Sent with a `Referer` to the Coding Plan page.\n  - Adds `Authorization: Bearer <access_token>` when available.\n  - Adds `GroupId` query param when known.\n- Region picker in Providers settings toggles the host; environment overrides:\n  - `MINIMAX_HOST=platform.minimaxi.com`\n  - `MINIMAX_CODING_PLAN_URL=...` (full URL override)\n  - `MINIMAX_REMAINS_URL=...` (full URL override)\n\n## Cookie capture (optional override)\n- Open the Coding Plan page and DevTools → Network.\n- Select the request to `/v1/api/openplatform/coding_plan/remains`.\n- Copy the `Cookie` request header (or use “Copy as cURL” and paste the whole line).\n- Paste into Preferences → Providers → MiniMax only if automatic import fails.\n\n## Notes\n- Cookies alone often return status 1004 (“cookie is missing, log in again”); the remains API expects a Bearer token.\n- MiniMax stores `access_token` in Chromium local storage (LevelDB). Some entries serialize the storage key without a scheme\n  (ex: `minimax.io`), so origin matching must account for host-only keys.\n- Raw JWT scan fallback remains as a safety net if Chromium key formats change.\n- If local storage keys don’t decode (some Chrome builds), the MiniMax-specific text scan avoids a full raw-byte scan.\n\n## Cookie file paths\n- Safari: `~/Library/Cookies/Cookies.binarycookies`\n- Chrome/Chromium forks: `~/Library/Application Support/Google/Chrome/*/Cookies`\n- Firefox: `~/Library/Application Support/Firefox/Profiles/*/cookies.sqlite`\n\n## Snapshot mapping\n- Primary: percent used from `model_remains` (used/total) or HTML \"Available usage\".\n- Window: derived from `start_time`/`end_time` or HTML duration text.\n- Reset: derived from `remains_time` (fallback to `end_time`) or HTML \"Resets in …\".\n- Plan/tier: best-effort from response fields or HTML title.\n\n## Key files\n- `Sources/CodexBarCore/Providers/MiniMax/MiniMaxUsageFetcher.swift`\n- `Sources/CodexBarCore/Providers/MiniMax/MiniMaxProviderDescriptor.swift`\n- `Sources/CodexBar/Providers/MiniMax/MiniMaxProviderImplementation.swift`\n"
  },
  {
    "path": "docs/ollama.md",
    "content": "---\nsummary: \"Ollama provider notes: settings scrape, cookie auth, and Cloud Usage parsing.\"\nread_when:\n  - Adding or modifying the Ollama provider\n  - Debugging Ollama cookie import or settings parsing\n  - Adjusting Ollama menu labels or usage mapping\n---\n\n# Ollama Provider\n\nThe Ollama provider scrapes the **Plan & Billing** page to extract Cloud Usage limits for session and weekly windows.\n\n## Features\n\n- **Plan badge**: Reads the plan tier (Free/Pro/Max) from the Cloud Usage header.\n- **Session + weekly usage**: Parses the percent-used values shown in the usage bars.\n- **Reset timestamps**: Uses the `data-time` attribute on the “Resets in …” elements.\n- **Browser cookie auth**: No API keys required.\n\n## Setup\n\n1. Open **Settings → Providers**.\n2. Enable **Ollama**.\n3. Leave **Cookie source** on **Auto** (recommended, imports Chrome cookies by default).\n\n### Manual cookie import (optional)\n\n1. Open `https://ollama.com/settings` in your browser.\n2. Copy a `Cookie:` header from the Network tab.\n3. Paste it into **Ollama → Cookie source → Manual**.\n\n## How it works\n\n- Fetches `https://ollama.com/settings` using browser cookies.\n- Parses:\n  - Plan badge under **Cloud Usage**.\n  - **Session usage** and **Weekly usage** percentages.\n  - `data-time` ISO timestamps for reset times.\n\n## Troubleshooting\n\n### “No Ollama session cookie found”\n\nLog in to `https://ollama.com/settings` in Chrome, then refresh in CodexBar.\nIf your active session is only in Safari (or another browser), use **Cookie source → Manual** and paste a cookie header.\n\n### “Ollama session cookie expired”\n\nSign out and back in at `https://ollama.com/settings`, then refresh.\n\n### “Could not parse Ollama usage”\n\nThe settings page HTML may have changed. Capture the latest page HTML and update `OllamaUsageParser`.\n"
  },
  {
    "path": "docs/opencode.md",
    "content": "---\nsummary: \"OpenCode provider notes: browser cookie import, _server endpoints, and usage parsing.\"\nread_when:\n  - Adding or modifying the OpenCode provider\n  - Debugging OpenCode usage parsing or cookie import\n---\n\n# OpenCode provider\n\n## Data sources\n- Browser cookies from `opencode.ai`.\n- `POST https://opencode.ai/_server` with server function IDs:\n  - `workspaces` (`def39973159c7f0483d8793a822b8dbb10d067e12c65455fcb4608459ba0234f`)\n  - `subscription.get` (`7abeebee372f304e050aaaf92be863f4a86490e382f8c79db68fd94040d691b4`)\n\n## Usage mapping\n- Primary window: rolling 5-hour usage (`rollingUsage.usagePercent`, `rollingUsage.resetInSec`).\n- Secondary window: weekly usage (`weeklyUsage.usagePercent`, `weeklyUsage.resetInSec`).\n- Resets computed as `now + resetInSec`.\n\n## Notes\n- Responses are `text/javascript` with serialized objects; parse via regex.\n- Missing workspace ID or usage fields should raise parse errors.\n- Cookie import defaults to Chrome-only to avoid extra browser prompts; pass a browser list to override.\n- Set `CODEXBAR_OPENCODE_WORKSPACE_ID` to skip workspace lookup and force a specific workspace.\n- Workspace override accepts a raw `wrk_…` ID or a full `https://opencode.ai/workspace/...` URL.\n- Cached cookies: Keychain cache `com.steipete.codexbar.cache` (account `cookie.opencode`, source + timestamp). Browser\n  import only runs when the cached cookie fails.\n"
  },
  {
    "path": "docs/openrouter.md",
    "content": "# OpenRouter Provider\n\n[OpenRouter](https://openrouter.ai) is a unified API that provides access to multiple AI models from different providers (OpenAI, Anthropic, Google, Meta, and more) through a single endpoint.\n\n## Authentication\n\nOpenRouter uses API key authentication. Get your API key from [OpenRouter Settings](https://openrouter.ai/settings/keys).\n\n### Environment Variable\n\nSet the `OPENROUTER_API_KEY` environment variable:\n\n```bash\nexport OPENROUTER_API_KEY=\"sk-or-v1-...\"\n```\n\n### Settings\n\nYou can also configure the API key in CodexBar Settings → Providers → OpenRouter.\n\n## Data Source\n\nThe OpenRouter provider fetches usage data from two API endpoints:\n\n1. **Credits API** (`/api/v1/credits`): Returns total credits purchased and total usage. The balance is calculated as `total_credits - total_usage`.\n\n2. **Key API** (`/api/v1/key`): Returns rate limit information for your API key.\n\n## Display\n\nThe OpenRouter menu card shows:\n\n- **Primary meter**: Credit usage percentage (how much of your purchased credits have been used)\n- **Balance**: Displayed in the identity section as \"Balance: $X.XX\"\n\n## CLI Usage\n\n```bash\ncodexbar --provider openrouter\ncodexbar -p or  # alias\n```\n\n## Environment Variables\n\n| Variable | Description |\n|----------|-------------|\n| `OPENROUTER_API_KEY` | Your OpenRouter API key (required) |\n| `OPENROUTER_API_URL` | Override the base API URL (optional, defaults to `https://openrouter.ai/api/v1`) |\n| `OPENROUTER_HTTP_REFERER` | Optional client referer sent as `HTTP-Referer` header |\n| `OPENROUTER_X_TITLE` | Optional client title sent as `X-Title` header (defaults to `CodexBar`) |\n\n## Notes\n\n- Credit values are cached on OpenRouter's side and may be up to 60 seconds stale\n- OpenRouter uses a credit-based billing system where you pre-purchase credits\n- Rate limits depend on your credit balance (10+ credits = 1000 free model requests/day)\n"
  },
  {
    "path": "docs/packaging.md",
    "content": "---\nsummary: \"Packaging, signing, and bundled CLI notes.\"\nread_when:\n  - Packaging/signing builds\n  - Updating bundle layout or CLI bundling\n---\n\n# Packaging & signing\n\n## Scripts\n- `Scripts/package_app.sh`: builds host arch by default; set `ARCHES=\"arm64 x86_64\"` for universal. Verifies slices.\n- `Scripts/compile_and_run.sh`: uses host arch; pass `--release-universal` or `--release-arches=\"arm64 x86_64\"` for release packaging.\n- `Scripts/sign-and-notarize.sh`: signs, notarizes, staples, zips (accepts `ARCHES` for universal).\n- `Scripts/make_appcast.sh`: generates Sparkle appcast and embeds HTML release notes.\n- `Scripts/changelog-to-html.sh`: converts the per-version changelog section to HTML for Sparkle.\n\n## Bundle contents\n- `CodexBarWidget.appex` bundled with app-group entitlements.\n- `CodexBarCLI` copied to `CodexBar.app/Contents/Helpers/` for symlinking.\n- SwiftPM resource bundles (e.g. `KeyboardShortcuts_KeyboardShortcuts.bundle`) copied into `Contents/Resources` (required for `KeyboardShortcuts.Recorder`).\n\n## Releases\n- Full checklist in `docs/RELEASING.md`.\n\nSee also: `docs/sparkle.md`.\n"
  },
  {
    "path": "docs/perf-energy-issue-139-main-fix-validation-2026-02-19.md",
    "content": "# CodexBar Issue #139 Main Fix Validation (Post-Fix vs Pre-Fix)\n\nDate: 2026-02-19\nWorkspace: /Users/michalkrsik/windsurf_project_folder/CodexBar\nBranch: codex/perf-issue-139\n\nReference pre-fix report:\n- /Users/michalkrsik/windsurf_project_folder/CodexBar/docs/perf-energy-issue-139-simulation-report-2026-02-19.md\n\n## Implemented Main Fix\n\nFile changed:\n- /Users/michalkrsik/windsurf_project_folder/CodexBar/Sources/CodexBarCore/Providers/Codex/CodexStatusProbe.swift\n\nBehavior change:\n- Primary Codex PTY probe timeout reduced from 18s to 8s.\n- Retry policy changed from `retry on parseFailed OR timedOut` to `retry only on parseFailed`.\n- Parse retry timeout set to 4s.\n- Timed-out runs now fail fast and wait for next scheduled refresh.\n\n## Post-Fix Validation Method\n\nTarget: main culprit path (Codex CLI failure path).\n\nPractical simulation used:\n- `CODEX_CLI_PATH` pointed to a fake codex script.\n- Script behavior:\n  - exits immediately for `app-server` args (forces RPC failure/fallback path),\n  - otherwise busy-loops with no `/status` output (simulates heavy stuck CLI PTY behavior).\n- Command run (3 times):\n  - `./.build/debug/CodexBarCLI usage --provider codex --source cli --format json --pretty`\n- Collected:\n  - wall time (`/usr/bin/time -p`),\n  - sampled child CPU every 0.5s,\n  - leftover child-process count after run.\n\nArtifacts:\n- /tmp/codexbar_main_fix_validation_after\n\n## Post-Fix Results (3 runs)\n\n| Run | Real (s) | Avg child CPU (%) | Max child CPU (%) | Remaining child procs |\n|---|---:|---:|---:|---:|\n| 1 | 12.76 | 88.32 | 100.00 | 0 |\n| 2 | 12.67 | 89.79 | 100.00 | 0 |\n| 3 | 12.59 | 89.90 | 100.00 | 0 |\n| Mean | 12.67 | 89.34 | 100.00 | 0 |\n\n## Side-by-Side Comparison Against Stored Pre-Fix Report\n\nPre-fix values are from the stored report's Culprit A simulation summary.\nPost-fix values are from the validation above.\n\n| Metric | Pre-fix (stored report) | Post-fix (this validation) | Delta |\n|---|---:|---:|---:|\n| Failed-run duration (worst-case path) | 42.00s (code-path budget before fix) | 12.67s (measured mean) | -69.8% |\n| Child CPU during failed run | 113.32% avg | 89.34% avg | -21.2% |\n| Peak child CPU during failed run | 115.90% max | 100.00% max | -13.7% |\n| Remaining child processes after failure | not captured in pre-fix report | 0 | improved |\n\nDerived CPU-time exposure index (avg CPU * duration):\n- Pre-fix: `113.32 * 42.00 = 4759.44`\n- Post-fix: `89.34 * 12.67 = 1132.94`\n- Reduction: **-76.2%**\n\n## Conclusion\n\nThe implemented main fix materially reduces the failure-path runtime and overall CPU exposure.\nThe heavy CLI process can still spike CPU while active, but it now lives for a much shorter window and is cleaned up after failure.\n"
  },
  {
    "path": "docs/perf-energy-issue-139-simulation-report-2026-02-19.md",
    "content": "# CodexBar Issue #139 Performance/Energy Simulation Report\n\nDate: 2026-02-19\nWorkspace: /Users/michalkrsik/windsurf_project_folder/CodexBar\nIssue: https://github.com/steipete/codexbar/issues/139\n\n## Purpose\n\nDetermine which suspected culprit(s) can produce the abnormal CPU/energy behavior reported by users, using short reproducible simulations and process-level sampling.\n\n## Host/Tooling\n\n- macOS: Darwin 25.2.0 (arm64)\n- Swift: 6.2.3\n- Sampling tools: `ps`, `top`\n- Note: `powermetrics` was unavailable (requires sudo password in this session), so energy was sampled via `top` `POWER` proxy.\n\n## Simulated Culprits\n\n- Culprit A: CLI/PTTY-style heavy subprocess churn with polling loop behavior.\n- Culprit B: Web dashboard scrape/retry loop with repeated parse work and 400-600ms waits.\n- Culprit C: 75ms idle polling loop (blink-style wakeups).\n- Combined: A + B + C at once.\n- Baseline: near-idle control.\n\n## Test Pass 1 (Primary Mechanism Pass)\n\nArtifacts:\n- /tmp/codexbar_perf_sim/results_20260219_111607\n\nSummary:\n\n| Scenario | Avg CPU | Max CPU | Avg RSS MB | Avg POWER | Avg IDLEW |\n|---|---:|---:|---:|---:|---:|\n| Baseline | 0.00 | 0.10 | 0.54 | 0.00 | 0.00 |\n| Culprit A | 113.68 | 117.40 | 121.76 | 0.00 | 0.00 |\n| Culprit B | 4.64 | 13.30 | 64.15 | 0.00 | 5.04 |\n| Culprit C | 0.25 | 2.30 | 33.12 | 0.00 | 10.43 |\n| Combined | 114.62 | 121.30 | 217.62 | 0.00 | 0.00 |\n\nInterpretation:\n- CPU ranking was clear (A dominates strongly).\n- POWER field in this pass was unusable (stuck at 0.00 for several scenarios due `top` sampling mode).\n\n## Test Pass 2 (Calibrated Energy Pass)\n\nArtifacts:\n- /tmp/codexbar_perf_sim/energy2_results_20260219_112350\n\nSampling correction:\n- Switched to `top -l 2` and parsed the second sample for tracked PIDs to get non-zero `POWER` values.\n\nSummary:\n\n| Scenario | Avg CPU | Max CPU | Avg RSS MB | Avg POWER | Max POWER | Avg IDLEW |\n|---|---:|---:|---:|---:|---:|---:|\n| Baseline | 0.00 | 0.00 | 0.55 | 0.00 | 0.00 | 0.00 |\n| Culprit A | 113.32 | 115.90 | 114.73 | 94.85 | 150.60 | 6106.70 |\n| Culprit B | 4.30 | 10.10 | 62.09 | 2.94 | 4.20 | 2.18 |\n| Culprit C | 0.35 | 2.60 | 34.09 | 0.23 | 0.60 | 14.27 |\n| Combined | 115.67 | 118.90 | 218.48 | 93.29 | 129.60 | 3858.60 |\n\n## Validation Against Expected Pattern\n\nComputed checks on pass 2: 10/10 passed.\n\n- A dominates CPU vs B (>=10x): PASS\n- A dominates CPU vs C (>=50x): PASS\n- A dominates POWER vs B (>=10x): PASS\n- A dominates POWER vs C (>=100x): PASS\n- Combined close to A CPU (+/-15%): PASS\n- Combined close to A POWER (+/-25%): PASS\n- C is low CPU (<1%): PASS\n- B is moderate CPU (<15%): PASS\n- Baseline near zero CPU (<1%): PASS\n- Baseline near zero POWER (<1): PASS\n\n## Final Finding\n\nPrimary root-cause class for the extreme behavior is Culprit A (heavy long-lived CLI/subprocess churn under bad/failure paths).\n\nSecondary:\n- Culprit B contributes moderate load.\n- Culprit C contributes wakeups/noise but is not a major CPU/energy driver.\n\nHuman-level answer:\nA tiny toolbar app should never keep heavyweight background subprocess/UI loops alive in failure conditions. That behavior is what creates the abnormal battery/CPU footprint.\n\n## Limitations\n\n- These were controlled simulations, not a full end-user UI replay of `CodexBar.app` with all real auth/cookie/account paths.\n- `powermetrics` could not be used in this session due sudo restriction.\n\n## Recommended Next Validation (Before Closing Issue)\n\n- Run one short real-app before/after validation after fixes:\n  - baseline\n  - culprit A-focused repro\n  - optional combined\n- Capture `powermetrics` if sudo is available, plus process CPU snapshots.\n- Publish before/after table in issue #139.\n"
  },
  {
    "path": "docs/provider.md",
    "content": "---\nsummary: \"Provider authoring guide: shared host APIs, provider boundaries, and how to add a new provider.\"\nread_when:\n  - Adding a new provider (usage + status + identity)\n  - Refactoring provider architecture or shared host APIs\n  - Reviewing provider boundaries (no identity leakage)\n---\n\n# Provider authoring guide\n\nGoal: adding a provider should feel like:\n- add one folder\n- define one descriptor + strategies\n- add one implementation (UI hooks only)\n- done (tests + docs)\n\nThis doc describes the **current provider architecture** (post-macro registry) and the exact steps to add a new provider.\n\n## Terms\n- **Provider**: a source of usage/quota/status data (Codex, Claude, Gemini, Antigravity, Cursor, …).\n- **Descriptor**: the single source of truth for labels, URLs, defaults, and fetch strategies.\n- **Fetch strategy**: one concrete way to obtain usage (CLI, web cookies, OAuth API, local probe, etc.).\n- **Host APIs**: shared capabilities we provide to providers (Keychain, browser cookies, PTY, HTTP, WebView scrape, token-cost).\n- **Identity fields**: email/org/plan/loginMethod. Must stay **siloed per provider**.\n\n## Architecture overview (now)\n- `Sources/CodexBarCore`: provider descriptors + fetch strategies + probes + parsing + shared utilities.\n- `Sources/CodexBar`: UI/state + provider implementations (settings/login/menu hooks only).\n- Provider IDs are compile-time: `UsageProvider` enum (used for persistence + widgets).\n- Provider wiring is descriptor-driven:\n  - `ProviderDescriptor` owns labels, URLs, default enablement, and fetch pipeline.\n  - `ProviderFetchStrategy` objects implement concrete fetch paths.\n  - CLI + app both call the same descriptor/fetch pipeline.\n\nCommon building blocks already exist:\n- PTY: `TTYCommandRunner`\n- subprocess: `SubprocessRunner`\n- cookie import: `BrowserCookieImporter` (Safari/Chrome/Firefox adapters)\n- OpenAI dashboard web scrape: `OpenAIDashboardFetcher` (WKWebView + JS)\n- cost usage: local log scanner (Codex + Claude)\n\nThe old “switch provider” wiring is gone. Everything should be driven by the descriptor and its strategies.\n\n## Provider descriptor (source of truth)\n\nIntroduce a single descriptor per provider:\n- `id` (stable `UsageProvider`)\n- display/labels/URLs (menu title, dashboard URL, status URL)\n- UI branding (icon name, primary color)\n- capabilities (supportsCredits, supportsTokenCost, supportsStatusPolling, supportsLogin)\n- fetch plan (allowed `--source` modes + ordered strategy pipeline)\n- CLI metadata (cliName, aliases, version provider)\n- account behavior (e.g., `usesAccountFallback` for Codex auth.json)\n\nUI and settings should become descriptor-driven:\n- no provider-specific branching for labels/links/toggle titles\n- minimal provider-specific UI (only when a provider truly needs bespoke UX)\n\n## Fetch strategies\n\nA provider declares a pipeline of strategies, in priority order. Each strategy:\n- advertises a `kind` (cli, web cookies, oauth, api token, local probe, web dashboard)\n- declares availability (checks settings, cookies, env vars, installed CLI)\n- fetches `UsageSnapshot` (and optional credits/dashboard)\n- can be filtered by CLI `--source` or app settings\n\nThe pipeline resolves to the best available strategy, and falls back on failure when allowed.\nEach run returns a `ProviderFetchOutcome` with **attempts + errors** for debug UI and CLI `--verbose`.\n\n## Host APIs are explicit, small, testable\nExpose a narrow set of protocols/structs that provider implementations can use:\n- `KeychainAPI`: read-only, allowlisted service/account pairs\n- `BrowserCookieAPI`: import cookies by domain list; returns cookie header + diagnostics\n- `PTYAPI`: run CLI interactions with timeouts + “send on substring” + stop rules\n- `HTTPAPI`: URLSession wrapper with domain allowlist + standard headers + tracing\n- `WebViewScrapeAPI`: WKWebView lease + `evaluateJavaScript` + snapshot dumping\n- `TokenCostAPI`: Cost Usage local-log integration (Codex/Claude today; extend later)\n- `StatusAPI`: status polling helpers (Statuspage + Workspace incidents)\n- `LoggerAPI`: scoped logger + redaction helpers\n\nRule: providers do not talk to `FileManager`, `Security`, or “browser internals” directly unless they *are* the host API implementation.\n\n## Provider-specific code layout\n- `Sources/CodexBarCore/Providers/<ProviderID>/`\n  - `<ProviderID>Descriptor.swift` (descriptor + strategy pipeline)\n  - `<ProviderID>Strategies.swift` (strategy implementations)\n  - `<ProviderID>Probe.swift` / `<ProviderID>Fetcher.swift`\n  - `<ProviderID>Models.swift`\n  - `<ProviderID>Parser.swift` (if text/HTML parsing)\n- `Sources/CodexBar/Providers/<ProviderID>/`\n  - `<ProviderID>ProviderImplementation.swift` (settings/login UI hooks only)\n\n## Minimal provider example (copy-paste)\n\n```swift\nimport CodexBarMacroSupport\nimport Foundation\n\n@ProviderDescriptorRegistration\n@ProviderDescriptorDefinition\npublic enum ExampleProviderDescriptor {\n    static func makeDescriptor() -> ProviderDescriptor {\n        ProviderDescriptor(\n            id: .example,\n            metadata: ProviderMetadata(\n                id: .example,\n                displayName: \"Example\",\n                sessionLabel: \"Session\",\n                weeklyLabel: \"Weekly\",\n                opusLabel: nil,\n                supportsOpus: false,\n                supportsCredits: false,\n                creditsHint: \"\",\n                toggleTitle: \"Show Example usage\",\n                cliName: \"example\",\n                defaultEnabled: false,\n                isPrimaryProvider: false,\n                usesAccountFallback: false,\n                dashboardURL: nil,\n                statusPageURL: nil),\n            branding: ProviderBranding(\n                iconStyle: .codex,\n                iconResourceName: \"ProviderIcon-example\",\n                color: ProviderColor(red: 0.2, green: 0.6, blue: 0.8)),\n            tokenCost: ProviderTokenCostConfig(\n                supportsTokenCost: false,\n                noDataMessage: { \"Example cost summary is not supported.\" }),\n            fetchPlan: ProviderFetchPlan(\n                sourceModes: [.auto, .cli],\n                pipeline: ProviderFetchPipeline(resolveStrategies: { _ in [ExampleFetchStrategy()] })),\n            cli: ProviderCLIConfig(\n                name: \"example\",\n                versionDetector: nil))\n    }\n}\n\nstruct ExampleFetchStrategy: ProviderFetchStrategy {\n    let id: String = \"example.cli\"\n    let kind: ProviderFetchKind = .cli\n\n    func isAvailable(_: ProviderFetchContext) async -> Bool { true }\n\n    func fetch(_: ProviderFetchContext) async throws -> ProviderFetchResult {\n        let usage = UsageSnapshot(\n            primary: .init(usedPercent: 0, windowMinutes: nil, resetsAt: nil, resetDescription: nil),\n            secondary: nil,\n            updatedAt: Date(),\n            identity: nil)\n        return self.makeResult(usage: usage, sourceLabel: \"cli\")\n    }\n\n    func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool { false }\n}\n```\n\n## Guardrails (non-negotiable)\n- Identity silo: never display identity/plan fields from provider A inside provider B UI.\n- Privacy: default to on-device parsing; browser cookies are opt-in and never persisted by us beyond WebKit stores.\n- Reliability: providers must be timeout-bounded; no unbounded waits on network/PTY/UI.\n- Degradation: prefer cached data over flapping; show clear errors when stale.\n\n## Adding a new provider (current flow)\n\nChecklist:\n- Add `UsageProvider` case in `Sources/CodexBarCore/Providers/Providers.swift`.\n- Create `Sources/CodexBarCore/Providers/<ProviderID>/`:\n  - `<ProviderID>Descriptor.swift`: define `ProviderDescriptor` + fetch pipeline.\n  - `<ProviderID>Strategies.swift`: implement one or more `ProviderFetchStrategy`.\n  - `<ProviderID>Probe.swift` / `<ProviderID>Fetcher.swift`: concrete fetcher logic.\n  - `<ProviderID>Models.swift`: snapshot structs.\n  - `<ProviderID>Parser.swift` (if needed).\n- Attach `@ProviderDescriptorRegistration` + `@ProviderDescriptorDefinition` to the descriptor type.\n  Implement `static func makeDescriptor() -> ProviderDescriptor`.\n- Attach `@ProviderImplementationRegistration` to the implementation type (macros auto-register).\n  - No manual list edits.\n- Add `Sources/CodexBar/Providers/<ProviderID>/<ProviderID>ProviderImplementation.swift`:\n  - `ProviderImplementation` only for settings/login UI hooks.\n- Add icons + color in descriptor:\n  - `iconName` must match `ProviderIcon-<id>` asset.\n  - Color used in menu cards + switcher.\n- If CLI-specific behavior is needed:\n  - add `cliName`, `cliAliases`, `sourceModes`, `versionProvider` in descriptor.\n  - strategies decide which `--source` modes apply.\n- Tests:\n  - `UsageSnapshot` mapping unit tests\n  - strategy availability + fallback tests\n  - CLI provider parsing (aliases + --source validation)\n- Docs:\n  - add provider section in `docs/providers.md` with data source + auth notes\n  - update `docs/provider.md` if the pipeline model changes\n\n## UI notes (Providers settings)\nCurrent: checkboxes per provider.\n\nPreferred direction: table/list rows (like a “sessions” table):\n- Provider (name + short auth hint)\n- Enabled toggle\n- Status (ok/stale/error + last updated)\n- Auth source (CLI / cookies / web / oauth) when applicable\n- Actions (Login / Diagnose / Copy debug log)\n\nThis keeps the pane scannable once we have >5 providers.\n"
  },
  {
    "path": "docs/providers.md",
    "content": "---\nsummary: \"Provider data sources and parsing overview (Codex, Claude, Gemini, Antigravity, Cursor, OpenCode, Alibaba Coding Plan, Droid/Factory, z.ai, Copilot, Kimi, Kilo, Kimi K2, Kiro, Warp, Vertex AI, Augment, Amp, Ollama, JetBrains AI, OpenRouter).\"\nread_when:\n  - Adding or modifying provider fetch/parsing\n  - Adjusting provider labels, toggles, or metadata\n  - Reviewing data sources for providers\n---\n\n# Providers\n\n## Fetch strategies (current)\nLegend: web (browser cookies/WebView), cli (RPC/PTy), oauth (API), api token, local probe, web dashboard.\nSource labels (CLI/header): `openai-web`, `web`, `oauth`, `api`, `local`, plus provider-specific CLI labels (e.g. `codex-cli`, `claude`).\n\nCookie-based providers expose a Cookie source picker (Automatic or Manual) in Settings → Providers.\nBrowser cookie imports are cached in Keychain (`com.steipete.codexbar.cache`, account `cookie.<provider>`) and reused\nuntil the session is invalid, to avoid repeated Keychain prompts.\n\n| Provider | Strategies (ordered for auto) |\n| --- | --- |\n| Codex | Web dashboard (`openai-web`) → CLI RPC/PTy (`codex-cli`); app uses CLI usage + optional dashboard scrape. |\n| Claude | App Auto: OAuth API (`oauth`) → CLI PTY (`claude`) → Web API (`web`). CLI Auto: Web API (`web`) → CLI PTY (`claude`). |\n| Gemini | OAuth API via Gemini CLI credentials (`api`). |\n| Antigravity | Local LSP/HTTP probe (`local`). |\n| Cursor | Web API via cookies → stored WebKit session (`web`). |\n| OpenCode | Web dashboard via cookies (`web`). |\n| Alibaba Coding Plan | Console RPC via web cookies (auto/manual) with API key fallback (`web`, `api`). |\n| Droid/Factory | Web cookies → stored tokens → local storage → WorkOS cookies (`web`). |\n| z.ai | API token (Keychain/env) → quota API (`api`). |\n| MiniMax | Manual cookie header (Keychain/env) → browser cookies (+ local storage access token) → coding plan page (HTML) with remains API fallback (`web`). |\n| Kimi | API token (JWT from `kimi-auth` cookie) → usage API (`api`). |\n| Kilo | API token (`KILO_API_KEY`) → usage API (`api`); auto falls back to CLI session auth (`cli`). |\n| Copilot | API token (device flow/env) → copilot_internal API (`api`). |\n| Kimi K2 | API key (Keychain/env) → credit endpoint (`api`). |\n| Kiro | CLI command via `kiro-cli chat --no-interactive \"/usage\"` (`cli`). |\n| Vertex AI | Google ADC OAuth (gcloud) → Cloud Monitoring quota usage (`oauth`). |\n| JetBrains AI | Local XML quota file (`local`). |\n| Amp | Web settings page via browser cookies (`web`). |\n| Warp | API token (config/env) → GraphQL request limits (`api`). |\n| Ollama | Web settings page via browser cookies (`web`). |\n| OpenRouter | API token (config, overrides env) → credits API (`api`). |\n\n## Codex\n- Web dashboard (when enabled): `https://chatgpt.com/codex/settings/usage` via WebView + browser cookies.\n- CLI RPC default: `codex ... app-server` JSON-RPC (`account/read`, `account/rateLimits/read`).\n- CLI PTY fallback: `/status` scrape.\n- Local cost usage: scans `~/.codex/sessions/**/*.jsonl` (last 30 days).\n- Status: Statuspage.io (OpenAI).\n- Details: `docs/codex.md`.\n\n## Claude\n- App Auto: OAuth API (`oauth`) → CLI PTY (`claude`) → Web API (`web`).\n- CLI Auto: Web API (`web`) → CLI PTY (`claude`).\n- Local cost usage: scans `~/.config/claude/projects/**/*.jsonl` (last 30 days).\n- Status: Statuspage.io (Anthropic).\n- Details: `docs/claude.md`.\n\n## z.ai\n- API token from Keychain or `Z_AI_API_KEY` env var.\n- Quota endpoint: `https://api.z.ai/api/monitor/usage/quota/limit` (global) or `https://open.bigmodel.cn/api/monitor/usage/quota/limit` (BigModel CN); override with `Z_AI_API_HOST` or `Z_AI_QUOTA_URL`.\n- Status: none yet.\n- Details: `docs/zai.md`.\n\n## MiniMax\n- Session cookie header from Keychain or `MINIMAX_COOKIE`/`MINIMAX_COOKIE_HEADER` env var.\n- Hosts: `platform.minimax.io` (global) or `platform.minimaxi.com` (China mainland) via region picker or `MINIMAX_HOST`; full overrides via `MINIMAX_CODING_PLAN_URL` / `MINIMAX_REMAINS_URL`.\n- `GET {host}/v1/api/openplatform/coding_plan/remains`.\n- Status: none yet.\n- Details: `docs/minimax.md`.\n\n## Kimi\n- Auth token (JWT from `kimi-auth` cookie) via manual entry or `KIMI_AUTH_TOKEN` env var.\n- `POST https://www.kimi.com/apiv2/kimi.gateway.billing.v1.BillingService/GetUsages`.\n- Shows weekly quota and 5-hour rate limit (300 minutes).\n- Status: none yet.\n- Details: `docs/kimi.md`.\n\n## Kilo\n- API token from `~/.codexbar/config.json` (`providers[].apiKey`) or `KILO_API_KEY`.\n- Auto mode tries API first and falls back to CLI auth when API credentials are missing or unauthorized.\n- CLI auth source: `~/.local/share/kilo/auth.json` (`kilo.access`), typically created by `kilo login`.\n- Usage endpoint: `https://app.kilo.ai/api/trpc`.\n- Status: none yet.\n- Details: `docs/kilo.md`.\n\n## Kimi K2\n- API key via Settings (Keychain) or `KIMI_K2_API_KEY`/`KIMI_API_KEY` env var.\n- `GET https://kimi-k2.ai/api/user/credits`.\n- Shows credit usage based on consumed/remaining totals.\n- Status: none yet.\n- Details: `docs/kimi-k2.md`.\n\n## Gemini\n- OAuth-backed quota API (`retrieveUserQuota`) using Gemini CLI credentials.\n- Token refresh via Google OAuth if expired.\n- Tier detection via `loadCodeAssist`.\n- Status: Google Workspace incidents (Gemini product).\n- Details: `docs/gemini.md`.\n\n## Antigravity\n- Local Antigravity language server (internal protocol, HTTPS on localhost).\n- `GetUserStatus` primary; `GetCommandModelConfigs` fallback.\n- Status: Google Workspace incidents (Gemini product).\n- Details: `docs/antigravity.md`.\n\n## Cursor\n- Web API via browser cookies (`cursor.com` + `cursor.sh`).\n- Fallback: stored WebKit session.\n- Status: Statuspage.io (Cursor).\n- Details: `docs/cursor.md`.\n\n## OpenCode\n- Web dashboard via browser cookies (`opencode.ai`).\n- `POST https://opencode.ai/_server` (workspaces + subscription usage).\n- Status: none yet.\n- Details: `docs/opencode.md`.\n\n## Alibaba Coding Plan\n- Web mode uses console RPC host (`bailian-singapore-cs.alibabacloud.com` for intl) with form payload + `sec_token`.\n- Cookie sources: browser import (`auto`) or manual header (`cookieSource: manual`).\n- API key fallback from Settings (`providerConfig.alibaba.apiKey`) or `ALIBABA_CODING_PLAN_API_KEY` env var.\n- Region hosts: international (`ap-southeast-1`) and China mainland (`cn-beijing`).\n- Overrides: `ALIBABA_CODING_PLAN_HOST` or `ALIBABA_CODING_PLAN_QUOTA_URL`.\n- Status: `https://status.aliyun.com` (link only, no auto-polling).\n- Details: `docs/alibaba-coding-plan.md`.\n\n## Droid (Factory)\n- Web API via Factory cookies, bearer tokens, and WorkOS refresh tokens.\n- Multiple fallback strategies (cookies → stored tokens → local storage → WorkOS cookies).\n- Status: `https://status.factory.ai`.\n- Details: `docs/factory.md`.\n\n## Copilot\n- GitHub device flow OAuth token + `api.github.com/copilot_internal/user`.\n- Status: Statuspage.io (GitHub).\n- Details: `docs/copilot.md`.\n\n## Kiro\n- CLI-based: runs `kiro-cli chat --no-interactive \"/usage\"` with 10s timeout.\n- Parses ANSI output for plan name, monthly credits percentage, and bonus credits.\n- Requires `kiro-cli` installed and logged in via AWS Builder ID.\n- Status: AWS Health Dashboard (manual link, no auto-polling).\n- Details: `docs/kiro.md`.\n\n## Warp\n- API token from Settings or `WARP_API_KEY` / `WARP_TOKEN` env var.\n- GraphQL credit limits: `https://app.warp.dev/graphql/v2?op=GetRequestLimitInfo`.\n- Shows monthly credits usage and next refresh time.\n- Status: none yet.\n- Details: `docs/warp.md`.\n\n## Vertex AI\n- OAuth credentials from `gcloud auth application-default login` (ADC).\n- Quota usage via Cloud Monitoring `consumer_quota` metrics for `aiplatform.googleapis.com`.\n- Token cost: scans `~/.claude/projects/` logs filtered to Vertex AI-tagged entries.\n- Requires Cloud Monitoring API access in the current project.\n- Details: `docs/vertexai.md`.\n## JetBrains AI\n- Local XML quota file from IDE configuration directory.\n- Auto-detects installed JetBrains IDEs; uses most recently used.\n- Reads `AIAssistantQuotaManager2.xml` for monthly credits and refill date.\n- Status: none (no status page).\n- Details: `docs/jetbrains.md`.\n\n## Amp\n- Web settings page (`https://ampcode.com/settings`) via browser cookies.\n- Parses Amp Free usage from the settings HTML.\n- Status: none yet.\n- Details: `docs/amp.md`.\n\n## Ollama\n- Web settings page (`https://ollama.com/settings`) via browser cookies.\n- Parses Cloud Usage plan badge, session/weekly usage, and reset timestamps.\n- Status: none yet.\n- Details: `docs/ollama.md`.\n\n## OpenRouter\n- API token from `~/.codexbar/config.json` (`providerConfig.openrouter.apiKey`) or `OPENROUTER_API_KEY` env var.\n- Credits endpoint: `https://openrouter.ai/api/v1/credits` (returns total credits purchased and usage).\n- Key info endpoint: `https://openrouter.ai/api/v1/key` (returns rate limit info).\n- Override base URL with `OPENROUTER_API_URL` env var.\n- Status: `https://status.openrouter.ai` (link only, no auto-polling yet).\n- Details: `docs/openrouter.md`.\n\nSee also: `docs/provider.md` for architecture notes.\n"
  },
  {
    "path": "docs/quotio-comparison.md",
    "content": "# Quotio Comparison & Enhancement Opportunities\n\n**Date:** 2026-01-04  \n**Source:** https://github.com/nguyenphutrong/quotio\n\n## Overview\n\nQuotio is a similar macOS menu bar app for AI quota tracking with **2k stars** and **117 forks**. It has a broader scope (proxy server + quota tracking) but shares many features with CodexBar.\n\n---\n\n## Key Differences\n\n### Architecture\n- **Quotio**: Manages a local proxy server (`CLIProxyAPI`) that routes requests to multiple AI providers\n- **CodexBar**: Direct provider monitoring only (no proxy layer)\n- **Implication**: Quotio is more complex but offers request routing/failover\n\n### Provider Support\n**Quotio supports:**\n- Gemini, Claude, OpenAI Codex, Qwen, Vertex AI, iFlow, Antigravity, Kiro, GitHub Copilot\n- **IDE monitoring**: Cursor, Trae (auto-detected, monitor-only)\n\n**CodexBar supports:**\n- Codex, Claude, Cursor, Gemini, Antigravity, Droid/Factory, Copilot, z.ai, Kiro, Vertex AI, **Augment**\n\n**Unique to CodexBar:**\n- Augment (we just added!)\n- z.ai\n- Droid/Factory\n\n**Unique to Quotio:**\n- Qwen, iFlow\n- Trae IDE monitoring\n\n---\n\n## Features We Should Consider\n\n### 1. **Auto-Warmup Scheduling** ⭐⭐⭐\n**What:** Automatically trigger 1-token model invocations on a schedule to keep accounts \"warm\"\n**Why:** Prevents cold-start delays, maintains session freshness\n**Implementation:** \n- Interval-based (15min-4h) or daily scheduling\n- Per-account model selection\n- Progress tracking in UI\n**Effort:** Medium (requires background task scheduler)\n**Value:** High for providers with session timeouts (Claude, Augment)\n\n### 2. **Account Switching for Antigravity** ⭐⭐\n**What:** Switch active Antigravity account by injecting OAuth tokens into IDE's SQLite database\n**Why:** Allows multi-account workflows without manual IDE logout/login\n**Implementation:**\n- Read/write to `~/Library/Application Support/Antigravity/User/globalStorage/state.vscdb`\n- Protobuf encode/decode for OAuth tokens\n- Process management (close IDE → backup → inject → restart)\n**Effort:** High (complex SQLite + protobuf + process management)\n**Value:** Medium (niche use case, but powerful for multi-account users)\n\n### 3. **Standalone Quota Mode** ⭐\n**What:** View quotas without running proxy server\n**Why:** Lighter weight, faster startup for quick checks\n**Implementation:** Already how CodexBar works! (We don't have a proxy)\n**Effort:** N/A (we already do this)\n**Value:** N/A (already implemented)\n\n### 4. **Smart Routing Strategies** ⭐⭐\n**What:** Round Robin or Fill First routing for multi-account setups\n**Why:** Optimize quota usage across accounts\n**Implementation:** Would require proxy layer (not applicable to CodexBar's architecture)\n**Effort:** Very High (requires proxy server)\n**Value:** Low for CodexBar (out of scope)\n\n### 5. **Custom Provider Support** ⭐⭐⭐\n**What:** User-defined AI providers with OpenAI-compatible, Claude, Gemini, Codex API configs\n**Why:** Extensibility for new/custom providers without code changes\n**Implementation:**\n- `CustomProvider` model with API type, base URL, auth\n- YAML config generation\n- UI for add/edit/delete custom providers\n**Effort:** Medium-High\n**Value:** High (future-proofs the app, community can add providers)\n\n### 6. **Multilingual Support** ⭐\n**What:** English, Vietnamese, Chinese, French\n**Why:** Broader user base\n**Implementation:** `.xcstrings` localization (we already have infrastructure)\n**Effort:** Medium (translation work)\n**Value:** Medium (depends on target audience)\n\n---\n\n## UI/UX Learnings\n\n### Menu Bar Design\n- **Quotio**: Custom provider icons in menu bar, quota overview popup\n- **CodexBar**: Two-bar meter icon, detailed menu card\n- **Takeaway**: Both approaches valid; CodexBar's meter is more info-dense\n\n### Settings Organization\n- **Quotio**: Dashboard, Providers, Agents, Quota, Logs, Settings tabs\n- **CodexBar**: Settings → Providers with per-provider toggles\n- **Takeaway**: Quotio's tab-based navigation is cleaner for complex apps\n\n### Quota Display\n- **Quotio**: Grid layout with tier badges (Pro/Ultra/Free), inline refresh\n- **CodexBar**: List-based with session/weekly bars\n- **Takeaway**: Tier badges are a nice visual touch\n\n---\n\n## Recommended Enhancements for CodexBar\n\n### Priority 1: Auto-Warmup Scheduling ⭐⭐⭐\n**Why:** Keeps sessions alive, prevents auth failures\n**Implementation:**\n1. Add `WarmupScheduler` service with interval/daily modes\n2. Per-provider warmup config in Settings\n3. Background task to trigger minimal API calls (1-token requests)\n4. UI to show last warmup time + next scheduled warmup\n\n**Providers that benefit:**\n- Claude (session expires)\n- Augment (session expires)\n- Codex (session expires)\n\n### Priority 2: Custom Provider Framework ⭐⭐⭐\n**Why:** Community extensibility, future-proof\n**Implementation:**\n1. `CustomProvider` model (name, icon, API type, base URL, auth)\n2. Settings UI for CRUD operations\n3. Generic quota fetcher that adapts to API type\n4. Save to UserDefaults or JSON file\n\n**Benefits:**\n- Users can add new providers without waiting for releases\n- Easier to experiment with beta/internal APIs\n\n### Priority 3: Enhanced Antigravity Support ⭐⭐\n**Why:** Quotio's account switching is powerful\n**Implementation:**\n1. Add `AntigravityAccountSwitcher` service\n2. SQLite database read/write for token injection\n3. Process manager for IDE restart\n4. UI confirmation dialog with progress states\n\n**Complexity:** High, but valuable for power users\n\n---\n\n## What NOT to Adopt\n\n### ❌ Proxy Server Layer\n- **Why:** Out of scope for CodexBar's mission (monitoring, not routing)\n- **Complexity:** Very high (server management, request routing, failover)\n- **Maintenance:** Ongoing burden\n\n### ❌ Agent Configuration\n- **Why:** Quotio auto-configures CLI tools (Claude Code, OpenCode, etc.) to use its proxy\n- **Relevance:** Not applicable without proxy layer\n\n---\n\n## Conclusion\n\n**Top 3 Enhancements:**\n1. **Auto-Warmup Scheduling** - Keeps sessions alive, prevents auth failures\n2. **Custom Provider Framework** - Future-proof, community-driven extensibility\n3. **Enhanced Antigravity Account Switching** - Power user feature\n\n**Quick Wins:**\n- Tier badges in quota display (visual polish)\n- Tab-based settings navigation (if app grows more complex)\n\n**Skip:**\n- Proxy server layer (out of scope)\n- Agent configuration (requires proxy)\n\n"
  },
  {
    "path": "docs/refactor/claude-current-baseline.md",
    "content": "---\nsummary: \"Current Claude behavior baseline before vNext refactor work.\"\nread_when:\n  - Planning Claude refactor tickets\n  - Changing Claude runtime/source selection\n  - Changing Claude OAuth prompt or cooldown behavior\n  - Changing Claude token-account routing\n---\n\n# Claude current baseline\n\nThis document is the current-state parity reference for Claude behavior in CodexBar.\n\nUse it when later tickets need to preserve or intentionally change Claude behavior. When the refactor plan,\nsummary docs, and running code disagree, treat current code plus characterization coverage as authoritative, and use\nthis document as the human-readable summary of that current state.\n\n## Scope of this baseline\n\nThis baseline captures the current behavior surface that later refactor work must preserve unless a future ticket\nchanges it intentionally:\n\n- runtime/source-mode selection,\n- prompt and cooldown behavior that affects Claude OAuth repair flows,\n- token-account routing at the app and CLI edges,\n- provider siloing and web-enrichment rules,\n- the current relationship between the public Claude doc and the vNext refactor plan.\n\n## Active behavior owners\n\nCurrent Claude behavior is defined by several active owners, not one central planner:\n\n- `Sources/CodexBarCore/Providers/Claude/ClaudeProviderDescriptor.swift`\n  owns the main provider-pipeline strategy order and fallback rules.\n- `Sources/CodexBarCore/Providers/Claude/ClaudeUsageFetcher.swift`\n  still owns a separate direct `.auto` path, delegated refresh, prompt/cooldown handling, and web-extra enrichment.\n- `Sources/CodexBar/Providers/Claude/ClaudeSettingsStore.swift`\n  owns app-side token-account routing into cookie or OAuth behavior.\n- `Sources/CodexBarCLI/TokenAccountCLI.swift`\n  owns CLI-side token-account routing and effective source-mode overrides.\n- `Sources/CodexBarCore/TokenAccountSupport.swift`\n  owns the current string heuristics that distinguish Claude OAuth access tokens from cookie/session-key inputs.\n\n## Current runtime and source-mode behavior\n\n### Main provider pipeline\n\nThe generic provider pipeline currently resolves Claude strategies in this order:\n\n| Runtime | Selected mode | Ordered strategies | Fallback behavior |\n| --- | --- | --- | --- |\n| app | auto | `oauth -> cli -> web` | OAuth can fall through to CLI/Web. CLI can fall through to Web only when Web is available. Web is terminal. |\n| app | oauth | `oauth` | No fallback. |\n| app | cli | `cli` | No fallback. |\n| app | web | `web` | No fallback. |\n| cli | auto | `web -> cli` | Web can fall through to CLI. CLI is terminal. |\n| cli | oauth | `oauth` | No fallback. |\n| cli | cli | `cli` | No fallback. |\n| cli | web | `web` | No fallback. |\n\nThis behavior is owned by `Sources/CodexBarCore/Providers/Claude/ClaudeProviderDescriptor.swift`\nthrough `ProviderFetchPlan` and `ProviderFetchPipeline`.\n\n### Other active `.auto` decision sites\n\nThe codebase still contains multiple active `.auto` decision sites:\n\n| Owner | Current behavior |\n| --- | --- |\n| `ClaudeProviderDescriptor.resolveUsageStrategy(...)` | Chooses `oauth`, then `cli`, then `web`, with final `cli` fallback when none are available. |\n| `ClaudeUsageFetcher.loadLatestUsage(.auto)` | Chooses `oauth`, then `web`, then `cli`, with final `oauth` fallback. |\n\nThis inconsistency is intentional to record here. RAT-107 directly characterizes the active direct-fetcher branches it\ncan reach cleanly in tests and records the remaining current-state behavior without reconciling it.\n\n## Prompt and cooldown baseline\n\nCurrent behavior that later refactor work must preserve:\n\n- The default Claude keychain prompt mode is `onlyOnUserAction`.\n- Prompt policy is only applicable when the Claude OAuth read strategy is `securityFramework`.\n- User-initiated interaction clears a prior Claude keychain cooldown denial before retrying availability or repair.\n- Startup bootstrap prompting is allowed only when all of these are true:\n  - runtime is app,\n  - interaction is background,\n  - refresh phase is startup,\n  - prompt mode is `onlyOnUserAction`,\n  - no cached Claude credentials exist.\n- Background delegated refresh is blocked when prompt policy is `onlyOnUserAction` and the caller did not explicitly\n  allow background delegated refresh.\n- Prompt mode `never` blocks delegated refresh attempts.\n- Expired credential owner behavior remains owner-specific:\n  - `.claudeCLI`: delegated refresh path,\n  - `.codexbar`: direct refresh path,\n  - `.environment`: no auto-refresh.\n\n## Token-account routing baseline\n\nAccepted Claude token-account input shapes today:\n\n- raw OAuth access token with `sk-ant-oat...` prefix,\n- `Bearer sk-ant-oat...` input,\n- raw session key,\n- full cookie header.\n\nCurrent routing rules:\n\n- OAuth-token-shaped inputs are not treated as cookies.\n- Cookie/header-shaped inputs are any value that already contains `Cookie:` or `=`.\n- App-side Claude snapshot behavior:\n  - OAuth token account keeps the usage source setting as-is, disables cookie mode (`.off`), clears the manual cookie\n    header, and relies on environment-token injection.\n  - Session-key or cookie-header account keeps the usage source setting as-is, forces manual cookie mode, and\n    normalizes raw session keys into `sessionKey=<value>`.\n- CLI-side Claude token-account behavior:\n  - OAuth token account changes the effective source mode from `auto` to `oauth`, disables cookie mode, omits a\n    manual cookie header, and injects `CODEXBAR_CLAUDE_OAUTH_TOKEN`.\n  - Session-key or cookie-header account stays in cookie/manual mode.\n\n## Siloing and web-enrichment baseline\n\nClaude Web enrichment is cost-only when the primary source is OAuth or CLI:\n\n- Web extras may populate `providerCost` when it is missing.\n- Web extras must not replace `accountEmail`, `accountOrganization`, or `loginMethod` from the primary source.\n- Snapshot identity remains provider-scoped to Claude.\n\nThis behavior is implemented in `Sources/CodexBarCore/Providers/Claude/ClaudeUsageFetcher.swift`\ninside `applyWebExtrasIfNeeded`.\n\n## Documentation contract\n\n- [docs/claude.md](../claude.md) is the summary doc for contributors who want an overview.\n- This file is the exact current-state baseline for contributor and refactor parity work.\n- [claude-provider-vnext-locked.md](claude-provider-vnext-locked.md)\n  is the future refactor plan and should cite this file for present behavior.\n\n## Characterization coverage\n\nStable automated coverage for this baseline lives in:\n\n- `Tests/CodexBarTests/ClaudeBaselineCharacterizationTests.swift`\n- `Tests/CodexBarTests/ClaudeOAuthFetchStrategyAvailabilityTests.swift`\n- `Tests/CodexBarTests/ClaudeUsageTests.swift`\n- `Tests/CodexBarTests/TokenAccountEnvironmentPrecedenceTests.swift`\n- `Tests/CodexBarTests/SettingsStoreCoverageTests.swift`\n\n`ClaudeUsageTests.swift` now directly characterizes the reachable `ClaudeUsageFetcher(.auto)` branches for:\n\n- OAuth when OAuth, Web, and CLI all appear available,\n- Web before CLI when OAuth is unavailable,\n\nThe successful CLI-selected branch and the CLI-failure-to-OAuth fallback remain documented from code inspection plus\nsurrounding Claude probe/regression coverage, because the current CLI-availability decision is sourced from process-wide\nbinary discovery with no stable test seam that would keep RAT-107 in scope.\n"
  },
  {
    "path": "docs/refactor/claude-provider-vnext-locked.md",
    "content": "---\nsummary: \"Locked implementation plan for Claude provider vNext: resolved source-selection contracts, typed credential rules, siloing guarantees, and phase gates.\"\nsupersedes: \"Initial vNext draft (removed)\"\ncreated: \"2026-02-18\"\nstatus: \"Locked for implementation\"\n---\n\n# Claude provider vNext (locked plan)\n\nThis is the implementation-locked vNext plan.\n\nIt preserves the original architecture direction, but removes ambiguity in behavior-critical areas before refactor work starts.\n\nCurrent-state parity reference for present behavior:\n\n- [docs/refactor/claude-current-baseline.md](claude-current-baseline.md)\n\nUse the baseline doc for present behavior. This vNext plan defines what the refactor should preserve and how it should\nbe staged; it is not the sole source of truth for current implementation details, and RAT-107 does not re-approve the\nrest of the future architecture below.\n\n## Assessment snapshot\n\n- **Approach score:** `8.4/10`.\n- **Why not 9+ yet:** the original plan left runtime ordering, token-account credential typing behavior, and compatibility mapping under-specified.\n- **How this doc closes the gap:** explicit contracts + resolved decisions + phase exit gates + risk checklist.\n- **Validated gap coverage in this version:** explicit `.auto` inconsistency handling, `ClaudeUsageFetcher` decomposition, stronger parity gates, TaskLocal-to-DI migration, and OAuth decomposition sub-phases.\n\n## Locked behavioral contracts\n\nThese behaviors are **non-negotiable** during refactor unless this doc is explicitly updated.\n\n### 1) Runtime + source-mode contract\n\n`ClaudeSourcePlanner` must reproduce this matrix exactly:\n\n| Runtime | Selected mode | Ordered attempts | Fallback rules |\n| --- | --- | --- | --- |\n| app | auto | oauth -> cli -> web | oauth fallback allowed; cli fallback to web only when web available; web terminal |\n| app | oauth | oauth | no fallback |\n| app | cli | cli | no fallback |\n| app | web | web | no fallback |\n| cli | auto | web -> cli | web fallback allowed to cli; cli terminal |\n| cli | oauth | oauth | no fallback |\n| cli | cli | cli | no fallback |\n| cli | web | web | no fallback |\n\nNotes:\n- `sourceLabel` remains the final step label for successful fetch output.\n- Planner diagnostics must include ordered steps and inclusion reasons.\n- Planner output must feed the existing generic provider fetch pipeline; do not introduce a second Claude-only\n  execution stack alongside `ProviderFetchPlan` / `ProviderFetchPipeline`.\n\n### 1a) `.auto` inconsistency characterization contract (must-do before reconciliation)\n\nCurrent code has three `.auto` decision sites with inconsistent app ordering:\n\n- Strategy pipeline resolve order (app): `oauth -> cli -> web`.\n- `resolveUsageStrategy` helper order: `oauth -> cli -> web -> cli fallback`.\n- `ClaudeUsageFetcher.loadLatestUsage(.auto)` order: `oauth -> web -> cli -> oauth fallback`.\n\nPhase 0 must characterize these paths with tests where they are reachable through stable seams, and otherwise defer to\nthe baseline doc before deleting any path.\nPhase 2 must reconcile this into planner-only source selection.\n\n### 2) Prompt/cooldown contract\n\nThe planner must use one explicit `ClaudePromptDecision` equivalent, but outcome parity with current behavior is required:\n\n- User-initiated actions can clear prior keychain cooldown denial.\n- Startup bootstrap prompt is only allowed when all are true:\n  - runtime is app\n  - interaction is background\n  - refresh phase is startup\n  - prompt mode is `onlyOnUserAction`\n  - no cached credentials\n- Background delegated refresh is blocked when:\n  - prompt policy is `onlyOnUserAction`\n  - caller does not explicitly allow background delegated refresh\n- Prompt mode `never` blocks delegated refresh attempts.\n\n### 3) Credential typing + routing contract\n\nTyped credentials must be introduced at the settings snapshot edge, with behavior parity:\n\n- `ClaudeManualCredential.sessionKey`\n- `ClaudeManualCredential.cookieHeader`\n- `ClaudeManualCredential.oauthAccessToken`\n\nAccepted Claude token-account inputs must continue to work:\n\n- Raw OAuth token (including `Bearer ...` input)\n- Raw session key\n- Full cookie header\n\nRouting parity requirements:\n\n- OAuth token account values must route to OAuth path (not cookie mode).\n- Cookie/session-key account values must route to web cookie path.\n- CLI token-account behavior must remain consistent in both app and `CodexBarCLI`.\n- Scope note: current string heuristics are mostly edge-routing logic, not deep OAuth credential decoding internals.\n\n### 4) Ownership and refresh contract\n\nCredential owner behavior must remain identical:\n\n- `.claudeCLI` expired credentials: delegated refresh path.\n- `.codexbar` expired credentials: direct refresh endpoint path.\n- `.environment` expired credentials: no auto-refresh.\n\nRefresh failure-gate semantics must remain unchanged.\n\n### 5) Provider siloing + enrichment contract\n\nHard invariant:\n\n- Never merge Claude Web identity into OAuth/CLI snapshots.\n- Web extras may enrich **cost only**.\n- Snapshot identity must always remain provider-scoped to `.claude` when persisted/displayed.\n\n### 6) Plan inference compatibility contract\n\nCanonical plan inference can live behind the existing `loginMethod` compatibility surface, but outward\ncompatibility must be preserved:\n\n- Existing detectable plans continue mapping to display strings:\n  - `Claude Max`\n  - `Claude Pro`\n  - `Claude Team`\n  - `Claude Enterprise`\n- Subscription detection behavior must remain compatible with current UI logic, including existing `Ultra` detection\n  semantics until that behavior is explicitly changed.\n\n### 7) Documentation + diagnostics contract\n\n- During refactor, characterization coverage plus\n  [docs/refactor/claude-current-baseline.md](claude-current-baseline.md)\n  are the source of truth when docs and code disagree.\n- `docs/claude.md` must be updated as part of Phase 0 after characterization lands so it no longer presents divergent\n  runtime ordering as settled behavior.\n- Debug surfaces must consume planner-derived diagnostics instead of recomputing Claude source decisions separately.\n\n## Resolved decisions (from open questions)\n\n### Web identity fill-ins\n\n- **Decision:** do not use Web identity to fill missing OAuth/CLI identity fields.\n- **Allowed:** Web cost enrichment only.\n\n### CLI runtime fallback ordering\n\n- **Decision:** keep current CLI ordering in `auto`: `web -> cli`.\n- Planner must encode this explicitly and not rely on incidental strategy ordering.\n\n### Startup bootstrap prompt in `onlyOnUserAction`\n\n- **Decision:** keep support exactly under the startup bootstrap constraints listed above.\n- Any expansion/restriction requires explicit doc update and tests.\n\n### Runtime policy unification timing\n\n- **Decision:** do not unify app and CLI `auto` ordering before this refactor.\n- First consolidate to one planner implementation with current runtime-specific behavior preserved.\n- Any runtime-policy unification is a separate, explicit behavior-change follow-up.\n\n### Planner integration timing\n\n- **Decision:** `ClaudeSourcePlanner` must be integrated into the existing provider descriptor / fetch pipeline rather\n  than added as a parallel orchestration layer.\n- Reuse current `ProviderFetchContext` / `ProviderFetchPlan` plumbing where possible.\n\n### Dependency seam timing\n\n- **Decision:** introduce dependency-container seams for newly extracted planner/executor components as they are\n  created.\n- Full TaskLocal cleanup can remain later, but new components should not deepen TaskLocal coupling.\n\n## Locked migration plan with exit gates\n\n### Phase 0: Baseline lock\n\nDeliverables:\n- Add `docs/refactor/claude-current-baseline.md` as the current-state behavior reference.\n- Add/refresh characterization tests for runtime/source matrix and prompt-decision parity.\n- Add explicit characterization tests for existing `.auto` decision paths where they are reachable through stable seams,\n  and defer remaining current-state details to the baseline doc until later reconciliation.\n- Update `docs/claude.md` after tests land so documented ordering matches characterized behavior.\n\nExit gate:\n- Behavior matrix tests pass for app and cli runtimes.\n- `.auto` characterization coverage plus the baseline doc record current divergence explicitly without forcing new\n  production seams in Phase 0.\n- `docs/claude.md` no longer contradicts characterized runtime/source behavior.\n\n### Phase 1: Canonical plan resolver\n\nDeliverables:\n- Introduce `ClaudePlan` + one resolver used by OAuth/Web/CLI mapping and downstream UI consumers.\n\nExit gate:\n- Plan mapping tests cover tier/billing/status-derived hints, compatibility display strings, and current UI subscription\n  detection compatibility.\n\n### Phase 1b: Typed credentials at the snapshot edge\n\nDeliverables:\n- Parse manual Claude credentials once at the app + CLI snapshot edges into a typed model.\n- Remove duplicated edge-routing heuristics for OAuth-vs-cookie decisions across settings snapshots and token-account\n  CLI code.\n\nExit gate:\n- Token account parity tests pass for app + CLI.\n- Snapshot-edge routing no longer duplicates Claude OAuth-token detection logic in multiple call sites.\n\n### Phase 2: Single source planner\n\nDeliverables:\n- Introduce `ClaudeSourcePlanner` + explicit `ClaudeFetchPlan`.\n- Integrate planner outputs into the existing provider pipeline / descriptor flow.\n- Remove duplicate `.auto` policy branches from lower layers.\n- Reconcile and remove `ClaudeUsageFetcher` internal `.auto` source selection.\n- Move debug/diagnostic surfaces to planner-derived attempt ordering instead of helper-specific recomputation.\n\nExit gate:\n- One authoritative planner path for mode/runtime ordering.\n- Fallback attempt logs still show expected sequence and source labels.\n- No surviving `.auto` source-order logic outside planner.\n- No surviving debug-only source-order recomputation outside planner diagnostics.\n- Old-vs-new planner parity tests pass before old branches are removed.\n\n### Phase 2b: `ClaudeUsageFetcher` decomposition\n\nDeliverables:\n- Split `ClaudeUsageFetcher` into smaller executor-focused components.\n- Extract delegated OAuth retry/recovery flow into dedicated units.\n- Remove embedded prompt-policy/source-selection ownership from fetcher; keep it execution-only.\n\nExit gate:\n- Fetcher no longer owns source-selection policy.\n- Delegated OAuth retry behavior is covered by dedicated tests and remains parity-compatible.\n\n### Phase 3: Test injection cleanup\n\nDeliverables:\n- Prefer dependency seams on extracted units instead of adding new TaskLocal-only override points.\n - Avoid expanding TaskLocal-only test hooks while decomposition work lands.\n\nExit gate:\n- New fetcher tests rely on explicit dependency seams where practical.\n\n### Phase 4: OAuth decomposition\n\nDeliverables (sub-phases):\n\n- **Phase 4a (repository extraction):**\n  - Extract IO + caching + owner/source loading into repository surface.\n  - Keep prompt-gate semantics unchanged.\n- **Phase 4b (refresher extraction):**\n  - Extract network refresh + failure gating to refresher component.\n  - Keep owner-based refresh behavior unchanged.\n- **Phase 4c (delegated controller extraction):**\n  - Extract delegated CLI touch + keychain-change observation + cooldown behavior.\n  - Keep delegated retry outcomes unchanged.\n\nExit gate:\n- Existing OAuth delegated refresh / prompt policy / cooldown suites pass without behavior deltas at each sub-phase.\n- Owner semantics parity remains intact across all sub-phases (`claudeCLI`, `codexbar`, `environment`).\n\n### Phase 5: Test injection migration (TaskLocal -> DI)\n\nDeliverables:\n- Move test injection from TaskLocal-heavy overrides to `ClaudeFetchDependencies` and explicit protocol stubs.\n- Keep compatibility shims temporarily where needed, then remove them.\n\nExit gate:\n- Core planner/executor tests run without TaskLocal injection dependencies.\n- Legacy TaskLocal-only override surfaces are either removed or isolated to compatibility adapters with deletion TODOs.\n\n### Phase 6: Web decomposition (optional)\n\nDeliverables:\n- Separate cookie acquisition from web usage client.\n- Keep probe tooling isolated behind debug/tool surface.\n\nExit gate:\n- Web parsing and account mapping tests remain green.\n\n## Implementation PR plan (stacked)\n\nUse this sequence to keep each PR reviewable without turning the rollout into unnecessary PR overhead.\n\n| PR | Title | Scope | Primary risks | Must-pass gate before merge |\n| --- | --- | --- | --- | --- |\n| PR-01 | Baseline characterization + doc correction | Lock current matrix behavior, characterize `.auto` paths through stable seams, defer remaining lower-level current-state details to the baseline doc, characterize prompt bootstrap/cooldown and token-account routing, then update docs to match reality. | R1, R2, R5, R6, R10 | No production behavior changes; characterization suites green; docs no longer contradict tests or the baseline. |\n| PR-02 | Canonical plan resolver | Introduce `ClaudePlan` and central resolver; map OAuth/Web/CLI/UI compatibility through one model while preserving current `loginMethod` projections. | R8 | Plan compatibility tests green (`Max/Pro/Team/Enterprise` + current subscription compatibility). |\n| PR-03 | Typed credentials at the edge | Parse manual credentials once (`sessionKey`, `cookieHeader`, `oauthAccessToken`) in app + CLI snapshot shaping. | R6 | Token-account routing parity tests green in app + CLI contexts. |\n| PR-04 | Source planner introduction + cutover | Add `ClaudeSourcePlanner`, prove parity against old path, then remove duplicate `.auto` selection branches once parity is proven. | R1, R5, R10 | One `.auto` authority remains; attempt/source-label diagnostics remain parity-compatible. |\n| PR-05 | `ClaudeUsageFetcher` decomposition | Split fetcher into execution/retry-focused units; remove embedded source-selection ownership. | R2, R10 | Delegated OAuth retry/recovery tests green with no behavior deltas. |\n| PR-06 | OAuth decomposition | Extract repository, refresher, and delegated-controller seams from `ClaudeOAuthCredentialsStore` while preserving owner semantics. | R3, R4, R7, R9 | Cache/fingerprint/prompt/owner suites green (`claudeCLI`, `codexbar`, `environment`). |\n| PR-07 (optional) | TaskLocal -> DI migration | Move remaining tests and seams to `ClaudeFetchDependencies`, keep temporary compat adapters, then remove. | R9 | Core planner/executor tests run without TaskLocal globals. |\n| PR-08 (optional) | Web decomposition | Split cookie acquisition from web usage client and keep tooling isolated. | R8, R10 | Web parsing/account mapping suites remain green. |\n\nStacking rules:\n\n1. Keep each PR scoped to one risk cluster and one merge gate.\n2. Do not remove old branches until a prior PR has old-vs-new parity tests in CI.\n3. If a PR intentionally changes behavior, update this locked doc in the same PR and call it out in summary.\n4. Prefer 6 core PRs unless parity risk forces a temporary split; do not fragment the rollout further without a\n   concrete rollback or reviewability reason.\n\n## Mandatory test additions\n\nAdd these test groups before or during Phases 1-3, then extend for later phases:\n\n1. Planner matrix tests:\n   - `(runtime x selected mode x interaction x refresh phase x availability)` -> exact step order + fallback.\n2. `.auto` divergence characterization tests:\n   - Lock current behavior of strategy pipeline vs `resolveUsageStrategy` helper vs fetcher-direct `.auto`.\n   - Use as guardrails while consolidating to planner-only logic.\n3. Typed credential parsing tests:\n   - OAuth token, bearer token, session key, cookie header, malformed strings.\n4. Cross-provider identity isolation tests:\n   - Ensure `.claude` identity does not leak via snapshot scoping/merging.\n5. Source-label and attempt diagnostics tests:\n   - Validate final source label and attempt list parity.\n6. CLI token-account parity tests:\n   - `TokenAccountCLIContext` and app settings snapshot behavior match for OAuth-vs-cookie routing.\n7. Old-vs-new parity tests:\n   - Compare old path and planner path outputs before branch removals in Phase 2 and Phase 2b.\n8. DI migration tests:\n   - Ensure new dependency container can drive planner/executor tests without TaskLocal globals.\n\n## Risk checklist (implementation review)\n\nUse these risk IDs in refactor PR checklists/reviews.\n\n| Risk ID | Severity | Risk | Detail |\n| --- | --- | --- | --- |\n| R1 | Critical | Auto-ordering reconciliation | Three `.auto` paths are inconsistent today. Characterize strategy pipeline vs `resolveUsageStrategy` helper vs fetcher-direct `.auto` before deleting any path. |\n| R2 | High | Prompt policy consolidation | Prompt policy exists across strategy availability, fetcher flow, and credentials store gates. Preserve startup bootstrap constraints exactly to avoid prompt storms or silent OAuth suppression. |\n| R3 | High | `ClaudeOAuthCredentialsStore` decomposition | Large lock-protected state + layered caches + fingerprint invalidation + security calls. Splits can break cache coherence, invalidation timing, or prompt gating order. |\n| R4 | High | Owner semantics drift | Preserve exact owner-to-refresh mapping: `.claudeCLI` delegated, `.codexbar` direct refresh, `.environment` no refresh. |\n| R5 | Medium | CLI runtime parity | Preserve runtime-specific policy: CLI `auto` remains `web -> cli`; OAuth is available only when explicitly selected as `sourceMode=.oauth`. Do not accidentally default CLI runtime to app ordering. |\n| R6 | Medium | Token-account OAuth-vs-cookie misrouting | Keep routing parity for OAuth token vs session key vs full cookie header, including `Bearer sk-ant-oat...` normalization. |\n| R7 | Medium | Cache invalidation regressions | Preserve credentials file/keychain fingerprint semantics and stale-cache guards during repository extraction. |\n| R8 | Low-Medium | Plan inference heuristic drift | Preserve web-specific plan inference fallback (`billing_type` + `rate_limit_tier`) when unifying plan resolution. |\n| R9 | Medium | Strict concurrency / `@Sendable` regressions | Maintain thread-safe behavior from current NSLock-based state while moving to DI/decomposed components under Swift 6 strict concurrency. |\n| R10 | Low | Debug/diagnostic drift | Keep source labels, attempt sequences, and debug output aligned with real planner decisions after consolidation. |\n\n## Change-control rule\n\nAny refactor PR that intentionally changes one of the locked contracts above must:\n\n1. Update this document.\n2. Add/adjust tests proving the new behavior.\n3. Call out the behavior change explicitly in the PR summary.\n"
  },
  {
    "path": "docs/refactor/cli.md",
    "content": "---\nsummary: \"CLI refactor plan: JSON-only errors, config validation, SettingsStore split.\"\nread_when:\n  - \"Refactoring CodexBar CLI error handling or config parsing.\"\n  - \"Splitting SettingsStore into smaller files.\"\n  - \"Adding config validation or CLI config commands.\"\n---\n\n# CLI Refactor Plan\n\n## Goals\n- JSON-only: every error is valid JSON on stdout (no mixed stderr).\n- Per-provider errors: provider failures yield provider-scoped error payloads.\n- Config validation: warn on invalid fields, unsupported source modes, bad regions.\n- Config parity: add CLI command to validate (and optionally dump) config.\n- SettingsStore split: files <500 LOC; clear separation (defaults vs config).\n\n## Constraints (keep)\n- Provider ordering stays driven by config `providers[]` order.\n- Provider enable/disable stays in config (`enabled`).\n- No Keychain persistence for provider secrets.\n- CLI still supports text output for non-JSON use.\n\n## Error JSON shape\n- JSON output remains an array for `usage` and `cost` commands.\n- Errors appear as payload entries with `error` set.\n- Global/CLI errors use `provider: \"cli\"` and `source: \"cli\"`.\n\n```json\n{\n  \"provider\": \"cli\",\n  \"source\": \"cli\",\n  \"error\": { \"code\": 1, \"message\": \"...\", \"kind\": \"config\" }\n}\n```\n\n## Config validation rules\n- `source` must be in provider descriptor `fetchPlan.sourceModes`.\n- `apiKey` only valid when provider supports `.api`.\n- `cookieSource` only valid when provider supports `.web`/`.auto`.\n- `region` only for `zai` or `minimax` with known values.\n- `workspaceID` only for `opencode`.\n- `tokenAccounts` only for providers in `TokenAccountSupportCatalog`.\n\n## CLI commands\n- `codexbar config validate`\n  - Prints JSON issues (or text summary).\n  - Exit non-zero if any errors.\n- (Optional) `codexbar config dump`\n  - Prints normalized config JSON.\n\n## Step-by-step implementation guide\n1. **Add validation types**\n   - `CodexBarConfigIssue` + `CodexBarConfigValidator` in `CodexBarCore/Config`.\n   - Keep file <500 LOC.\n2. **Hook validation into CLI**\n   - New `config validate` command.\n   - `--json-only` emits JSON array of issues.\n3. **Unify CLI error reporting**\n   - Parse `--json-only` early.\n   - Route all exits through a JSON-aware reporter.\n   - Use provider-scoped errors where possible.\n4. **Split CLIEntry.swift**\n   - Extract helpers + payload structs into dedicated files (<500 LOC each).\n5. **Split SettingsStore.swift**\n   - Move config-backed computed properties to `SettingsStore+Config.swift`.\n   - Move defaults-backed computed properties to `SettingsStore+Defaults.swift`.\n   - Move provider detection to `SettingsStore+ProviderDetection.swift`.\n6. **Provider toggles cleanup**\n   - Remove unused `ProviderToggleStore` + tests; keep migrator path for legacy toggles.\n7. **Tests**\n   - CLI json-only error payloads (invalid source, invalid provider selection).\n   - Config validation (bad region/source/apiKey field).\n   - SettingsStore order/toggle invariants still pass.\n8. **Verification**\n   - `swift test`, `swiftformat Sources Tests`, `swiftlint --strict`, `pnpm check`.\n   - `./Scripts/compile_and_run.sh`.\n   - CLI e2e: `codexbar --json-only ...`, `codexbar config validate`.\n\n"
  },
  {
    "path": "docs/refactor/macros.md",
    "content": "---\nsummary: \"Provider macro refactor ideas and follow-ups.\"\nread_when:\n  - Reviewing provider macro ergonomics\n  - Planning provider/descriptor refactors\n---\n\n# Macro refactor ideas\n\n1. Macro ergonomics + errors\n- Emit compile-time errors when a macro is attached to the wrong target or missing `descriptor`/`init`.\n- Add a member macro to generate `static let descriptor` from `makeDescriptor()` to remove boilerplate.\n\n2. Descriptor/data shape\n- Split ProviderDescriptor into Descriptor + FetchPlan + Branding + CLI files for cleaner deps.\n- Move source label into fetch results (strategy-specific).\n\n3. Fetching pipeline\n- Return all attempted strategies + errors for debug UI and CLI `--verbose`.\n\n4. Registry order & stability\n- Use ProviderDescriptorRegistry registration order for `all`; no sorting by rawValue.\n- Use metadata flags (e.g. `isPrimaryProvider`) instead of hard-coded Codex/Claude.\n\n5. Account/identity\n- Replace Codex UI special casing with a descriptor flag (e.g. `usesAccountFallback`).\n- Enforce provider-keyed identity in snapshots to avoid cross-provider display.\n\n6. Settings + tokens\n- Move Zai/Copilot token lookup into strategy-local resolvers (Keychain/env).\n- Add optional per-provider settings fields in ProviderSettingsSnapshot.\n\n7. Docs + tests\n- Add a minimal provider example in docs/provider.md.\n- Add registry completeness + deterministic-order test.\n"
  },
  {
    "path": "docs/refresh-loop.md",
    "content": "---\nsummary: \"Refresh cadence, background updates, and error handling.\"\nread_when:\n  - Changing refresh cadence, background tasks, or refresh triggers\n  - Investigating refresh timing or stale data behavior\n---\n\n# Refresh loop\n\n## Cadence\n- `RefreshFrequency`: Manual, 1m, 2m, 5m (default), 15m, 30m.\n- Stored in `UserDefaults` via `SettingsStore`.\n\n## Behavior\n- Background refresh runs off-main and updates `UsageStore` (usage + credits + optional web scrape).\n- Manual “Refresh now” always available in the menu.\n- Stale/error states dim the icon and surface status in-menu.\n\n## Optional future\n- Auto-seed a log if none exists via `codex exec --skip-git-repo-check --json \"ping\"` (currently not executed).\n\nSee also: `docs/status.md`, `docs/ui.md`.\n"
  },
  {
    "path": "docs/releasing-homebrew.md",
    "content": "---\nsummary: \"Homebrew Cask release steps for CodexBar (Sparkle-disabled builds).\"\nread_when:\n  - Publishing a CodexBar release via Homebrew\n  - Updating the Homebrew tap cask definition\n---\n\n# CodexBar Homebrew Release Playbook\n\nHomebrew is for the UI app via Cask. When installed via Homebrew, CodexBar disables Sparkle and shows a \"update via brew\" hint in About.\n\n## Prereqs\n- Homebrew installed.\n- Access to the tap repo: `../homebrew-tap`.\n\n## 1) Release CodexBar normally\nFollow `docs/RELEASING.md` to publish `CodexBar-<version>.zip` to GitHub Releases.\n\n## 2) Update the Homebrew tap cask\nIn `../homebrew-tap`, add/update the cask at `Casks/codexbar.rb`:\n- `url` points at the GitHub release asset: `.../releases/download/v<version>/CodexBar-<version>.zip`\n- Update `sha256` to match that zip.\n- Keep `depends_on arch: :arm64` and `depends_on macos: \">= :sonoma\"` (CodexBar is macOS 14+).\n\n## 2b) Update the Homebrew tap formula (Linux CLI)\nIn `../homebrew-tap`, add/update the formula at `Formula/codexbar.rb`:\n- `url` points at the GitHub release assets:\n  - `.../releases/download/v<version>/CodexBarCLI-v<version>-linux-aarch64.tar.gz`\n  - `.../releases/download/v<version>/CodexBarCLI-v<version>-linux-x86_64.tar.gz`\n- Update both `sha256` values to match those tarballs.\n\n## 3) Verify install\n```sh\nbrew uninstall --cask codexbar || true\nbrew untap steipete/tap || true\nbrew tap steipete/tap\nbrew install --cask steipete/tap/codexbar\nopen -a CodexBar\n```\n\n## 4) Push tap changes\nCommit + push in the tap repo.\n"
  },
  {
    "path": "docs/session-keepalive-design.md",
    "content": "# Session Keepalive Enhancement Design\n\n**Date:** 2026-01-04  \n**Feature:** Proactive session management for all providers  \n**Inspiration:** Quotio's \"Auto-Warmup\" feature\n\n---\n\n## Problem Statement\n\n**Current State:**\n- Augment has reactive keepalive (checks every 5min, refreshes when cookies near expiry)\n- Other providers (Claude, Codex) have no automatic session management\n- Users experience auth failures when sessions expire during idle periods\n- Manual re-authentication is disruptive to workflow\n\n**Pain Points:**\n1. **Session expiration during idle time** - User comes back to expired session\n2. **No proactive refresh** - We only react to expiration, don't prevent it\n3. **Provider-specific logic** - Each provider needs custom keepalive code\n4. **No user control** - Can't configure refresh intervals or disable keepalive\n\n---\n\n## Proposed Solution: Unified Session Keepalive System\n\n### Alternative Names (Better than \"Warmup\")\n1. **Session Keepalive** ✅ (current Augment terminology)\n2. **Session Refresh** (more accurate - we're refreshing, not warming)\n3. **Auto-Refresh** (simple, user-friendly)\n4. **Session Maintenance** (professional, clear intent)\n5. **Stay Alive** (casual, friendly)\n\n**Recommendation:** **\"Session Keepalive\"** - already used in codebase, technically accurate\n\n---\n\n## Architecture\n\n### 1. Core Components\n\n#### `SessionKeepaliveManager` (New)\n```swift\n@MainActor\npublic final class SessionKeepaliveManager {\n    // Unified scheduler for all providers\n    private var scheduledTasks: [Provider: Task<Void, Never>] = [:]\n    \n    // Per-provider configuration\n    private var configs: [Provider: KeepaliveConfig] = [:]\n    \n    // Start keepalive for a provider\n    func start(provider: Provider, config: KeepaliveConfig)\n    \n    // Stop keepalive for a provider\n    func stop(provider: Provider)\n    \n    // Force immediate refresh\n    func forceRefresh(provider: Provider) async\n}\n```\n\n#### `KeepaliveConfig` (New)\n```swift\npublic struct KeepaliveConfig {\n    enum Mode {\n        case interval(TimeInterval)  // Every X seconds\n        case daily(hour: Int, minute: Int)  // Daily at specific time\n        case beforeExpiry(buffer: TimeInterval)  // X seconds before expiry\n    }\n    \n    let mode: Mode\n    let enabled: Bool\n    let minRefreshInterval: TimeInterval  // Rate limiting\n}\n```\n\n### 2. Provider Integration\n\n#### Augment (Existing - Refactor)\n- **Current:** `AugmentSessionKeepalive` (standalone actor)\n- **New:** Integrate with `SessionKeepaliveManager`\n- **Strategy:** `beforeExpiry(buffer: 300)` - refresh 5min before cookie expiry\n- **Action:** Ping session endpoint + re-import cookies\n\n#### Claude (New)\n- **Strategy:** `interval(1800)` - refresh every 30 minutes\n- **Action:** Run `/status` command via CLI to keep session alive\n- **Fallback:** OAuth token refresh if CLI unavailable\n\n#### Codex (New)\n- **Strategy:** `interval(3600)` - refresh every hour\n- **Action:** OAuth token refresh using refresh token\n- **Benefit:** Prevents \"session expired\" errors during long coding sessions\n\n#### Cursor, Gemini, etc. (Future)\n- **Strategy:** TBD based on provider auth mechanism\n- **Extensible:** Protocol-based design allows easy addition\n\n---\n\n## Implementation Plan\n\n### Phase 1: Core Infrastructure (Week 1)\n1. Create `SessionKeepaliveManager` actor\n2. Define `KeepaliveConfig` model\n3. Add UserDefaults persistence for per-provider configs\n4. Create Settings UI for keepalive configuration\n\n### Phase 2: Provider Integration (Week 2)\n1. **Augment:** Refactor existing `AugmentSessionKeepalive` to use new manager\n2. **Claude:** Implement CLI-based keepalive\n3. **Codex:** Implement OAuth token refresh keepalive\n\n### Phase 3: UI & Polish (Week 3)\n1. Settings → Providers → [Provider] → \"Session Keepalive\" section\n2. Toggle: Enable/Disable\n3. Mode picker: Interval / Daily / Before Expiry\n4. Status indicator: Last refresh time, next scheduled refresh\n5. Manual \"Refresh Now\" button\n\n---\n\n## Settings UI Design\n\n```\nSettings → Providers → Augment\n\n┌─────────────────────────────────────────┐\n│ Session Keepalive                       │\n├─────────────────────────────────────────┤\n│ ☑ Keep session alive automatically      │\n│                                         │\n│ Mode: ⦿ Before Expiry                   │\n│       ○ Every 30 minutes                │\n│       ○ Daily at 9:00 AM                │\n│                                         │\n│ Last refresh: 2 minutes ago             │\n│ Next refresh: in 3 minutes              │\n│                                         │\n│ [Refresh Now]                           │\n└─────────────────────────────────────────┘\n```\n\n---\n\n## Key Differences from Quotio's \"Warmup\"\n\n| Aspect | Quotio Warmup | CodexBar Keepalive |\n|--------|---------------|-------------------|\n| **Purpose** | Trigger 1-token API calls to reset quota counters | Refresh auth sessions to prevent expiration |\n| **Target** | Antigravity only (quota reset) | All providers (session management) |\n| **Action** | Make minimal API request (costs 1 token) | Refresh auth tokens/cookies (free) |\n| **Benefit** | Faster quota resets during peak hours | Uninterrupted access, no auth failures |\n| **Cost** | Consumes tokens | No cost (just auth refresh) |\n\n**Why different?**\n- Quotio has a proxy server that routes requests → warmup keeps quota fresh\n- CodexBar monitors providers directly → keepalive prevents auth expiration\n\n---\n\n## Benefits\n\n### For Users\n1. **No more \"session expired\" errors** during active work\n2. **Seamless experience** - auth just works\n3. **Configurable** - control refresh frequency per provider\n4. **Transparent** - see when last refresh happened\n\n### For Developers\n1. **Unified system** - one manager for all providers\n2. **Extensible** - easy to add new providers\n3. **Testable** - clear separation of concerns\n4. **Maintainable** - centralized keepalive logic\n\n---\n\n## Technical Considerations\n\n### Rate Limiting\n- Minimum 2-minute interval between refreshes (prevent API abuse)\n- Exponential backoff on failures\n- Max 3 retry attempts before disabling\n\n### Error Handling\n- Log failures but don't crash\n- Notify user if keepalive fails repeatedly\n- Automatic disable after 5 consecutive failures\n\n### Performance\n- Background tasks use `.utility` priority\n- No blocking of main thread\n- Minimal memory footprint\n\n### Privacy\n- No data sent to external servers\n- All refresh actions are local (CLI, OAuth, cookie import)\n- User can disable entirely\n\n---\n\n## Next Steps\n\n1. **Review this design** - Get feedback on architecture\n2. **Prototype core manager** - Build `SessionKeepaliveManager`\n3. **Integrate Augment** - Refactor existing keepalive\n4. **Add Claude support** - Implement CLI-based refresh\n5. **Build Settings UI** - Per-provider configuration\n6. **Test & iterate** - Ensure reliability\n\n---\n\n## Open Questions\n\n1. **Should keepalive be enabled by default?**\n   - Pro: Better UX out of the box\n   - Con: Users might not want automatic refreshes\n   - **Recommendation:** Enabled by default, easy to disable\n\n2. **What's the default refresh interval?**\n   - Augment: 5min before expiry (existing)\n   - Claude: 30min interval\n   - Codex: 60min interval\n   - **Recommendation:** Conservative defaults, user-configurable\n\n3. **Should we show keepalive status in menu bar?**\n   - Pro: Transparency, user knows it's working\n   - Con: Clutters UI\n   - **Recommendation:** Show in Settings only, not menu bar\n\n"
  },
  {
    "path": "docs/site.css",
    "content": ":root {\n  color-scheme: light dark;\n  --bg: #070b12;\n  --panel: rgba(14, 18, 28, 0.78);\n  --panel-solid: #0e121c;\n  --text: rgba(241, 246, 255, 0.92);\n  --muted: rgba(187, 198, 218, 0.78);\n  --line: rgba(255, 255, 255, 0.1);\n  --accent: #3ff0d6;\n  --accent2: #2d5bff;\n  --shadow: rgba(0, 0, 0, 0.42);\n  --radius: 18px;\n  --radius-sm: 14px;\n}\n\n@media (prefers-color-scheme: light) {\n  :root {\n    --bg: #f8fbff;\n    --panel: rgba(255, 255, 255, 0.74);\n    --panel-solid: #ffffff;\n    --text: rgba(11, 16, 22, 0.92);\n    --muted: rgba(56, 70, 88, 0.75);\n    --line: rgba(11, 16, 22, 0.1);\n    --shadow: rgba(11, 16, 22, 0.12);\n  }\n}\n\n* {\n  box-sizing: border-box;\n}\n\nhtml,\nbody {\n  min-height: 100%;\n}\n\nbody {\n  margin: 0;\n  font: 16px/1.55 ui-sans-serif, \"Avenir Next\", Avenir, \"Segoe UI\", \"Helvetica Neue\", sans-serif;\n  color: var(--text);\n  background:\n    radial-gradient(1100px 800px at 15% 10%, rgba(63, 240, 214, 0.18), transparent 58%),\n    radial-gradient(950px 780px at 85% 20%, rgba(45, 91, 255, 0.18), transparent 60%),\n    radial-gradient(900px 700px at 40% 95%, rgba(63, 240, 214, 0.06), transparent 60%),\n    repeating-linear-gradient(90deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0.04) 1px, transparent 1px, transparent 72px),\n    var(--bg);\n}\n\nhtml {\n  background: var(--bg);\n}\n\na,\na:visited {\n  color: inherit;\n}\n\na {\n  text-decoration: none;\n}\n\na:hover {\n  text-decoration: underline;\n}\n\ncode {\n  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", monospace;\n  font-size: 0.95em;\n}\n\n.wrap {\n  max-width: 1020px;\n  margin: 0 auto;\n  padding: 46px 22px 34px;\n}\n\n.top {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 18px;\n  margin-bottom: 28px;\n}\n\n.brand {\n  display: flex;\n  align-items: center;\n  gap: 14px;\n  min-width: 240px;\n}\n\n.name {\n  display: flex;\n  flex-direction: column;\n  gap: 2px;\n}\n\n.title {\n  margin: 0;\n  font-family: ui-serif, \"New York\", \"Iowan Old Style\", Palatino, serif;\n  font-size: 22px;\n  letter-spacing: 0.1px;\n}\n\n.tagline {\n  margin: 0;\n  color: var(--muted);\n  font-size: 13.5px;\n}\n\n.mark {\n  width: 44px;\n  height: 44px;\n  border-radius: 13px;\n  border: 0;\n  box-shadow: none;\n  background: transparent;\n  object-fit: cover;\n  filter: drop-shadow(0 12px 24px rgba(0, 0, 0, 0.35));\n}\n\n.brand:hover .mark {\n  filter: drop-shadow(0 16px 34px rgba(0, 0, 0, 0.42));\n}\n\n.links {\n  display: flex;\n  gap: 10px;\n}\n\n.btn {\n  padding: 10px 14px;\n  border-radius: 13px;\n  border: 0;\n  background: color-mix(in srgb, var(--panel-solid) 32%, transparent);\n  box-shadow: 0 10px 28px var(--shadow);\n  transition: transform 160ms ease, background 160ms ease, box-shadow 160ms ease;\n  white-space: nowrap;\n}\n\n.btn:hover {\n  transform: translateY(-1px);\n  text-decoration: none;\n  background: color-mix(in srgb, var(--panel-solid) 52%, transparent);\n  box-shadow: 0 14px 34px var(--shadow);\n}\n\n.btn.primary {\n  background: linear-gradient(135deg, rgba(63, 240, 214, 0.65), rgba(45, 91, 255, 0.62));\n  background-clip: padding-box;\n  box-shadow: 0 12px 30px color-mix(in srgb, var(--accent) 25%, var(--shadow));\n}\n\n.btn.ghost {\n  background: transparent;\n  box-shadow: none;\n  color: color-mix(in srgb, var(--text) 86%, var(--muted));\n}\n\n.btn.ghost:hover {\n  background: color-mix(in srgb, var(--panel-solid) 28%, transparent);\n  box-shadow: 0 10px 24px var(--shadow);\n}\n\n.hero {\n  display: grid;\n  grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.9fr);\n  gap: 26px;\n  align-items: center;\n  padding: 24px 26px;\n  border: 1px solid var(--line);\n  border-radius: var(--radius);\n  background: var(--panel);\n  backdrop-filter: blur(12px);\n  box-shadow: 0 18px 60px var(--shadow);\n  position: relative;\n  overflow: hidden;\n}\n\n.copy {\n  position: relative;\n  z-index: 1;\n}\n\n.hero-heading {\n  display: flex;\n  gap: 14px;\n  align-items: flex-start;\n}\n\n.hero-icon {\n  width: 40px;\n  height: 40px;\n  border-radius: 12px;\n  filter: drop-shadow(0 12px 24px rgba(0, 0, 0, 0.35));\n}\n\n.hero::before {\n  content: \"\";\n  position: absolute;\n  inset: -1px;\n  background:\n    radial-gradient(600px 320px at 10% 15%, rgba(63, 240, 214, 0.18), transparent 55%),\n    radial-gradient(520px 300px at 92% 18%, rgba(45, 91, 255, 0.18), transparent 55%);\n  pointer-events: none;\n  opacity: 0.9;\n}\n\n.copy h2 {\n  margin: 0 0 8px;\n  font-family: ui-serif, \"New York\", \"Iowan Old Style\", Palatino, serif;\n  font-size: 34px;\n  letter-spacing: -0.3px;\n  line-height: 1.12;\n  position: relative;\n}\n\n.copy p {\n  margin: 0 0 10px;\n  color: var(--muted);\n}\n\n.requirement {\n  margin: 6px 0 0;\n  color: color-mix(in srgb, var(--text) 92%, var(--muted));\n  font-size: 13.5px;\n}\n\n.badges {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 8px;\n  margin-bottom: 10px;\n  position: relative;\n}\n\n.badge {\n  font-size: 12px;\n  padding: 6px 10px;\n  border-radius: 999px;\n  border: 1px solid var(--line);\n  background: color-mix(in srgb, var(--panel-solid) 14%, transparent);\n  color: color-mix(in srgb, var(--text) 86%, var(--muted));\n  letter-spacing: 0.2px;\n}\n\n.cta {\n  display: flex;\n  gap: 10px;\n  flex-wrap: wrap;\n  margin-top: 14px;\n  position: relative;\n}\n\n.brew-line {\n  display: flex;\n  align-items: center;\n  gap: 8px;\n  flex-wrap: wrap;\n}\n\n.brew-line code {\n  background: color-mix(in srgb, var(--panel-solid) 40%, transparent);\n  padding: 4px 8px;\n  border-radius: 6px;\n  border: 1px solid var(--line);\n}\n\n.copy-btn {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  width: 28px;\n  height: 28px;\n  padding: 0;\n  border: 1px solid var(--line);\n  border-radius: 6px;\n  background: color-mix(in srgb, var(--panel-solid) 30%, transparent);\n  color: var(--muted);\n  cursor: pointer;\n  transition: background 160ms ease, color 160ms ease, border-color 160ms ease;\n}\n\n.copy-btn:hover {\n  background: color-mix(in srgb, var(--panel-solid) 60%, transparent);\n  color: var(--text);\n  border-color: color-mix(in srgb, var(--accent) 32%, var(--line));\n}\n\n.copy-btn .icon-check {\n  display: none;\n  color: var(--accent);\n}\n\n.copy-btn.copied .icon-copy {\n  display: none;\n}\n\n.copy-btn.copied .icon-check {\n  display: block;\n}\n\n.copy-btn.copied {\n  border-color: var(--accent);\n  color: var(--accent);\n}\n\n.bullets {\n  margin: 14px 0 0;\n  padding: 0 0 0 18px;\n  color: var(--muted);\n  font-size: 13.5px;\n  position: relative;\n}\n\n.bullets li {\n  margin: 6px 0;\n}\n\n.note {\n  margin: 10px 0 0;\n  color: var(--muted);\n  font-size: 13px;\n}\n\n.shot {\n  margin: 0;\n  position: relative;\n  z-index: 1;\n}\n\n.shot img {\n  width: 100%;\n  height: auto;\n  border-radius: var(--radius-sm);\n  border: 1px solid var(--line);\n  box-shadow: 0 18px 60px var(--shadow);\n  background: color-mix(in srgb, var(--panel-solid) 92%, transparent);\n  transform: perspective(900px) rotateY(-6deg) rotateX(2deg);\n  transition: transform 220ms ease, box-shadow 220ms ease;\n}\n\n.hero-shot {\n  margin: 0;\n  justify-self: end;\n}\n\n.hero-shot img {\n  max-width: 300px;\n  margin-left: auto;\n}\n\n.hero-shot figcaption {\n  max-width: 300px;\n  text-align: center;\n  margin-left: auto;\n}\n\n.shot figcaption {\n  margin-top: 8px;\n  color: var(--muted);\n  font-size: 12.5px;\n  text-align: center;\n}\n\n.shot img:hover {\n  transform: perspective(900px) rotateY(-2deg) rotateX(0deg) translateY(-2px);\n  box-shadow: 0 24px 80px var(--shadow);\n}\n\n.details {\n  display: grid;\n  grid-template-columns: repeat(3, 1fr);\n  gap: 14px;\n  margin-top: 14px;\n}\n\n.details.permissions {\n  grid-template-columns: 1fr;\n}\n\n.card {\n  border: 1px solid var(--line);\n  border-radius: var(--radius);\n  padding: 14px 14px 12px;\n  background: var(--panel);\n  backdrop-filter: blur(10px);\n  box-shadow: 0 14px 40px var(--shadow);\n}\n\n.card h3 {\n  margin: 0 0 6px;\n  font-size: 13px;\n  letter-spacing: 0.2px;\n  text-transform: uppercase;\n  color: color-mix(in srgb, var(--text) 88%, var(--muted));\n}\n\n.card p {\n  margin: 0;\n  color: var(--muted);\n  font-size: 13.5px;\n}\n\n.foot {\n  margin-top: 22px;\n  text-align: center;\n  color: var(--muted);\n  font-size: 13px;\n}\n\n.links-row {\n  margin-top: 14px;\n  display: flex;\n  gap: 10px;\n  flex-wrap: wrap;\n  justify-content: center;\n}\n\n.link-pill {\n  padding: 8px 12px;\n  border-radius: 999px;\n  border: 1px solid var(--line);\n  background: color-mix(in srgb, var(--panel-solid) 24%, transparent);\n  font-size: 13px;\n}\n\n.foot a {\n  text-decoration: underline;\n  text-decoration-color: color-mix(in srgb, var(--muted) 45%, transparent);\n}\n\n[data-animate] {\n  opacity: 0;\n  transform: translateY(10px);\n  animation: rise 680ms cubic-bezier(0.2, 0.9, 0.2, 1) forwards;\n  animation-delay: calc(var(--d, 0) * 90ms);\n}\n\n[data-animate=\"1\"] {\n  --d: 1;\n}\n\n[data-animate=\"2\"] {\n  --d: 2;\n}\n\n[data-animate=\"3\"] {\n  --d: 3;\n}\n\n[data-animate=\"4\"] {\n  --d: 4;\n}\n\n[data-animate=\"5\"] {\n  --d: 5;\n}\n\n[data-animate=\"6\"] {\n  --d: 6;\n}\n\n[data-animate=\"7\"] {\n  --d: 7;\n}\n\n[data-animate=\"8\"] {\n  --d: 8;\n}\n\n@keyframes rise {\n  to {\n    opacity: 1;\n    transform: translateY(0);\n  }\n}\n\n@media (prefers-reduced-motion: reduce) {\n  [data-animate] {\n    animation: none;\n    opacity: 1;\n    transform: none;\n  }\n  .btn,\n  .shot img {\n    transition: none;\n  }\n}\n\n@media (max-width: 860px) {\n  .top {\n    flex-direction: column;\n    align-items: flex-start;\n  }\n  .hero {\n    grid-template-columns: 1fr;\n  }\n  .details {\n    grid-template-columns: 1fr;\n  }\n  .shot img {\n    transform: none;\n  }\n}\n"
  },
  {
    "path": "docs/sparkle.md",
    "content": "---\nsummary: \"Sparkle integration details for CodexBar: updater config, keys, and release flow.\"\nread_when:\n  - Touching Sparkle settings, feed URL, or keys\n  - Generating or troubleshooting the Sparkle appcast\n  - Validating update toggles or updater UI\n---\n\n# Sparkle integration\n\n- Framework: Sparkle 2.8.1 via SwiftPM.\n- Updater: `SPUStandardUpdaterController` owned by `AppDelegate` (see `Sources/CodexBar/CodexbarApp.swift:1`).\n- Feed: `SUFeedURL` in Info.plist points to GitHub Releases appcast (`appcast.xml`).\n- Key: `SUPublicEDKey` set to `AGCY8w5vHirVfGGDGc8Szc5iuOqupZSh9pMj/Qs67XI=`. Keep the Ed25519 private key safe; use it when generating the appcast.\n- UI: auto-check toggle (About) enables auto-downloads; menu only shows “Update ready, restart now?” once an update is downloaded.\n- LSUIElement: works; updater window will show when checking. App is non-sandboxed.\n- Channels: stable vs beta are served from the same appcast. Beta items are tagged with `sparkle:channel=\"beta\"`; About → Update Channel controls `allowedChannels`.\n\n## Release flow\n1) Build & notarize as usual (`./Scripts/sign-and-notarize.sh`), producing notarized `CodexBar-<ver>.zip`.\n2) Generate appcast entry with Sparkle `generate_appcast` using the Ed25519 private key; HTML release notes come from `CHANGELOG.md` via `Scripts/changelog-to-html.sh`. For beta releases: set `SPARKLE_CHANNEL=beta` to tag the entry.\n3) Upload `appcast.xml` + zip to GitHub Releases (feed URL stays stable).\n4) Tag/release.\n\n## Notes\n- HTML release notes are embedded in the appcast entry; the Sparkle update dialog should show formatted bullets (not raw tags).\n- If you change the feed host or key, update Info.plist (`SUFeedURL`, `SUPublicEDKey`) and bump the app.\n- Auto-check toggle is persisted via Sparkle; manual “Check for Updates…” remains in About.\n- CodexBar disables Sparkle in Homebrew and unsigned builds; those installs should be updated via `brew` or reinstalling from Releases.\n"
  },
  {
    "path": "docs/status.md",
    "content": "---\nsummary: \"Provider status checks, sources, and indicator mapping.\"\nread_when:\n  - Changing status sources or status UI\n  - Debugging status polling or incident parsing\n---\n\n# Status checks\n\n## Sources\n- OpenAI + Claude + Cursor + Factory + Copilot: Statuspage.io `api/v2/status.json`.\n- Gemini + Antigravity: Google Workspace incidents feed for the Gemini product.\n\n## Behavior\n- Toggle: Settings → Advanced → “Check provider status”.\n- `UsageStore` polls status and stores `ProviderStatus` for indicator/description.\n- Menu shows incident summary + freshness; icon overlays indicator.\n\n## Workspace incidents\n- Feed: `https://www.google.com/appsstatus/dashboard/incidents.json`.\n- Uses the Gemini product ID from provider metadata.\n- Chooses the most severe active incident for the provider.\n\n## Links\n- If `statusPageURL` is set, status polling uses it and the menu action opens it.\n- If only `statusLinkURL` exists, the menu action opens it without polling.\n\nSee also: `docs/providers.md`.\n"
  },
  {
    "path": "docs/ui.md",
    "content": "---\nsummary: \"Menu bar UI, icon rendering, and menu layout details.\"\nread_when:\n  - Changing menu layout, icon rendering, or UI copy\n  - Updating menu card or provider-specific UI\n---\n\n# UI & icon\n\n## Menu bar\n- LSUIElement app: no Dock icon; status item uses custom NSImage.\n- Merge Icons toggle combines providers into one status item with a switcher.\n- When Overview has selected providers, the switcher includes an Overview tab that renders up to 3 provider rows.\n- Overview row order follows provider order; selecting a row jumps to that provider detail card.\n\n## Icon rendering\n- 18×18 template image.\n- Top bar = 5-hour window; bottom hairline = weekly window.\n- Fill represents percent remaining by default; “Show usage as used” flips to percent used.\n- Dimmed when last refresh failed; status overlays render incident indicators.\n- Advanced: menu bar can show provider branding icons with a percent label instead of critter bars.\n\n## Menu card\n- Session + weekly rows with resets (countdown by default; optional absolute clock display).\n- Codex-only: Credits + “Buy Credits…” in-card action.\n- Web-only rows (when OpenAI cookies are enabled): code review remaining, usage breakdown submenu.\n- Token accounts: optional account switcher bar or stacked account cards (up to 6) when multiple manual tokens exist.\n\n## Pace tracking\n\nPace compares your actual usage against an even-consumption budget that would spread your allowance evenly across the reset window.\n\n- **On pace** – usage matches the expected rate.\n- **X% in deficit** – you're consuming faster than the even rate; at this pace you'll run out before the window resets.\n- **X% in reserve** – you're consuming slower than the even rate; you have headroom to spare.\n\nWhen usage is in deficit, the right-hand label shows an estimated \"Runs out in …\" countdown. When usage will last until the reset, it shows \"Lasts until reset\".\n\nPace is calculated for Codex and Claude weekly windows only and is hidden when less than 3% of the window has elapsed.\n\n## Preferences notes\n- Advanced: “Disable Keychain access” turns off browser cookie import; paste Cookie headers manually in Providers.\n- Display: “Overview tab providers” controls which providers appear in Merge Icons → Overview (up to 3).\n- If no providers are selected for Overview, the Overview tab is hidden.\n- Providers → Claude: “Keychain prompt policy” controls Claude OAuth prompt behavior (Never / Only on user action /\n  Always allow prompts).\n- When “Disable Keychain access” is enabled in Advanced, the Claude keychain prompt policy remains visible but is\n  inactive.\n\n## Widgets (high level)\n- Widget entries mirror the menu card; detailed pipeline in `docs/widgets.md`.\n\nSee also: `docs/widgets.md`.\n"
  },
  {
    "path": "docs/vertexai.md",
    "content": "---\nsummary: \"Vertex AI provider data sources: gcloud ADC credentials and Cloud Monitoring quota usage.\"\nread_when:\n  - Debugging Vertex AI auth or quota fetch\n  - Updating Vertex AI usage mapping or login flow\n---\n\n# Vertex AI provider\n\n## Data sources + fallback order\n1) **OAuth via gcloud ADC** (only path used in `fetch()`):\n   - Reads `application_default_credentials.json` from the gcloud config directory.\n   - Uses Cloud Monitoring time-series metrics to compute quota usage.\n\n## OAuth credentials\n- Authenticate: `gcloud auth application-default login`.\n- Project: `gcloud config set project PROJECT_ID`.\n- Fallback project env vars: `GOOGLE_CLOUD_PROJECT`, `GCLOUD_PROJECT`, `CLOUDSDK_CORE_PROJECT`.\n\n## API endpoints\n- Cloud Monitoring timeSeries:\n  - Usage: `serviceruntime.googleapis.com/quota/allocation/usage`\n  - Limit: `serviceruntime.googleapis.com/quota/limit`\n  - Resource: `consumer_quota` with `service=\"aiplatform.googleapis.com\"`.\n\n## Mapping\n- Matches usage + limit series by quota metric + limit name + location.\n- Reports the highest usage percent across matched series.\n- Displayed as \"Quota usage\" with period \"Current quota\".\n\n## Token Cost Tracking\n\nVertex AI Claude usage is logged to the same local files as direct Anthropic API usage (`~/.claude/projects/`). CodexBar identifies Vertex AI entries using two methods:\n\n### Detection Methods\n\n1. **Model name format** (primary): Vertex AI uses `@` as version separator\n   - Vertex AI: `claude-opus-4-5@20251101`\n   - Anthropic API: `claude-opus-4-5-20251101`\n\n2. **Metadata fields** (fallback): Entries with provider metadata\n   - `metadata.provider: \"vertexai\"`\n   - Keys containing `vertex` or `gcp`\n\n### Requirements\n\n**To see Vertex AI token costs:**\n1. Enable the **Vertex AI** provider in Settings → Providers\n2. Enable \"Show cost summary\" in Settings → General\n3. Use Claude Code with Vertex AI (your `cv` alias sets `ANTHROPIC_MODEL=claude-opus-4-5@20251101`)\n\n**Note:** The model name must include the `@` format for detection to work. If Claude Code normalizes model names to `-` format when logging, the entries won't be distinguishable from direct Anthropic API usage.\n\n## Troubleshooting\n- **No quota data**: Ensure Cloud Monitoring API access in the selected project.\n- **No cost data**: Check that `~/.claude/projects/` exists and contains `.jsonl` files from Claude Code usage with Vertex AI metadata.\n- **Auth issues**: Re-run `gcloud auth application-default login`.\n"
  },
  {
    "path": "docs/warp.md",
    "content": "---\nsummary: \"Warp provider notes: API token setup and request limit parsing.\"\nread_when:\n  - Adding or modifying the Warp provider\n  - Debugging Warp API tokens or request limits\n  - Adjusting Warp usage labels or reset behavior\n---\n\n# Warp Provider\n\nThe Warp provider reads credit limits from Warp's GraphQL API using an API token.\n\n## Features\n\n- **Monthly credits usage**: Shows credits used vs. plan limit.\n- **Reset timing**: Displays the next refresh time when available.\n- **Token-based auth**: Uses API key stored in Settings or env vars.\n\n## Setup\n\n1. Open **Settings → Providers**\n2. Enable **Warp**\n3. In Warp, open your profile menu → **Settings → Platform → API Keys**, then create a key.\n4. Enter the created `wk-...` key in CodexBar.\n\nReference guide: `https://docs.warp.dev/reference/cli/api-keys`\n\n### Environment variables (optional)\n\n- `WARP_API_KEY`\n- `WARP_TOKEN`\n\n## How it works\n\n- Endpoint: `https://app.warp.dev/graphql/v2?op=GetRequestLimitInfo`\n- Query: `GetRequestLimitInfo`\n- Fields used: `isUnlimited`, `nextRefreshTime`, `requestLimit`, `requestsUsedSinceLastRefresh` (API uses request-named fields for credits)\n\nIf `isUnlimited` is true, the UI shows “Unlimited” and a full remaining bar.\n\n## Troubleshooting\n\n### “Missing Warp API key”\n\nAdd a key in **Settings → Providers → Warp**, or set `WARP_API_KEY`.\n\n### “Warp API error”\n\nConfirm the token is valid and that your network can reach `app.warp.dev`.\n"
  },
  {
    "path": "docs/web-integration.md",
    "content": "---\nsummary: \"Deprecated: OpenAI web dashboard integration is documented in docs/codex.md.\"\nread_when:\n  - Redirect: see docs/codex.md\n---\n\n# OpenAI web integration (Codex)\n\nMoved to `docs/codex.md`.\n"
  },
  {
    "path": "docs/webkit.md",
    "content": "---\nsummary: \"WebKit usage in CodexBar: WebView hosts, cookie stores, and teardown guidance.\"\nread_when:\n  - Touching WebKit-backed login windows or offscreen scraping\n  - Debugging WebKit teardown crashes (especially x86_64)\n  - Changing WebKit window lifecycle behavior\n---\n\n# WebKit usage\n\nCodexBar uses WebKit in two places:\n- Visible login/purchase windows (e.g. Cursor login, credits purchase).\n- Offscreen WebViews for OpenAI dashboard scraping.\n\n## Teardown helper\n\nUse `WebKitTeardown` whenever a `WKWebView` + `NSWindow` should be torn down or hidden.\nIt centralizes Intel-friendly cleanup:\n- Stops loading and clears delegates.\n- Delays teardown so WebKit can unwind callbacks.\n- Keeps owners retained briefly on x86_64 to avoid autorelease crashes.\n\n## When to use\n\n- Any WebKit window that closes after a flow (login, purchase, etc.).\n- Offscreen WebView hosts that hide/close after scraping.\n\n## Notes\n\n- For login flows that persist cookies manually, prefer `WKWebsiteDataStore.nonPersistent()`.\n- Keep window `isReleasedWhenClosed = false` when teardown is deferred.\n- Avoid closing WebViews directly; route cleanup through the helper.\n"
  },
  {
    "path": "docs/widgets.md",
    "content": "---\nsummary: \"WidgetKit snapshot pipeline + visibility troubleshooting for CodexBar widgets.\"\nread_when:\n  - Modifying WidgetKit extension behavior or snapshot format\n  - Debugging widget update timing\n  - Widget gallery shows no CodexBar widgets\n---\n\n# Widgets\n\n## Snapshot pipeline\n- `WidgetSnapshotStore` writes compact JSON snapshots to the app-group container.\n- Widgets read the snapshot and render usage/credits/history states.\n\n## Extension\n- `Sources/CodexBarWidget` contains timeline + views.\n- Keep data shape in sync with `WidgetSnapshot` in the main app.\n\n## Visibility troubleshooting (macOS 14+)\nWhen widgets do not appear in the gallery at all, the issue is almost always\nregistration, signing, or daemon caching (not SwiftUI code).\n\n### 1) Verify the extension bundle exists where macOS expects it\n```\nAPP=\"/Applications/CodexBar.app\"\nWAPPEX=\"$APP/Contents/PlugIns/CodexBarWidget.appex\"\n\nls -la \"$WAPPEX\" \"$WAPPEX/Contents\" \"$WAPPEX/Contents/MacOS\"\n```\n\n### 2) PlugInKit registration (pkd)\n```\npluginkit -m -p com.apple.widgetkit-extension -v | grep -i codexbar || true\npluginkit -m -p com.apple.widgetkit-extension -i com.steipete.codexbar.widget -vv\n```\nNotes:\n- `+` = elected to use, `-` = ignored (PlugInKit elections).\n- If missing or ignored, force-add and re-elect:\n```\npluginkit -a \"$WAPPEX\"\npluginkit -e use -p com.apple.widgetkit-extension -i com.steipete.codexbar.widget\n```\n- Check for duplicates (old installs or version precedence):\n```\npluginkit -m -D -p com.apple.widgetkit-extension -i com.steipete.codexbar.widget -vv\n```\nIf multiple paths appear, delete older installs and bump `CFBundleVersion`.\n\n### 3) Code signing + Gatekeeper assessment\nWidgets are loaded by system daemons. Any signing failure can hide the widget.\n```\ncodesign --verify --deep --strict --verbose=4 /Applications/CodexBar.app\ncodesign --verify --strict --verbose=4 \"$WAPPEX\"\ncodesign --verify --strict --verbose=4 \"$WAPPEX/Contents/MacOS/CodexBarWidget\"\nspctl --assess --type execute --verbose=4 /Applications/CodexBar.app\n```\n\n### 4) Restart the right daemons (NotificationCenter alone is not enough)\n```\nkillall -9 pkd || true\nsudo killall -9 chronod || true\nkillall Dock NotificationCenter || true\n```\n\n### 5) Watch logs while opening the widget gallery\n```\nlog stream --style compact --predicate '(process == \"pkd\" OR process == \"chronod\" OR subsystem CONTAINS \"PlugInKit\" OR subsystem CONTAINS \"WidgetKit\")'\n```\n\n### 6) Packaging sanity checks\n- Widget bundle id should be `com.steipete.codexbar.widget`.\n- `NSExtensionPointIdentifier` must be `com.apple.widgetkit-extension`.\n- Bundle folder name should match: `CodexBarWidget.appex`.\n\nOptional: re-seed LaunchServices (rarely helps, but low risk):\n```\n/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -seed\n```\n\n## Common post-visibility issue: stale data\nIf the widget appears but always shows preview data:\n- App writes snapshot to fallback path while widget reads app-group container.\n- Validate that both app and widget resolve the same app-group container.\n\nSee also: `docs/ui.md`, `docs/packaging.md`.\n"
  },
  {
    "path": "docs/zai.md",
    "content": "---\nsummary: \"z.ai provider data sources: API token in config/env and quota API response parsing.\"\nread_when:\n  - Debugging z.ai token storage or quota parsing\n  - Updating z.ai API endpoints\n---\n\n# z.ai provider\n\nz.ai is API-token based. No browser cookies.\n\n## Token sources (fallback order)\n1) Config token (`~/.codexbar/config.json` → `providers[].apiKey`).\n2) Environment variable `Z_AI_API_KEY`.\n\n### Config location\n- `~/.codexbar/config.json`\n\n## API endpoint\n- `GET https://api.z.ai/api/monitor/usage/quota/limit`\n- BigModel (China mainland) host: `https://open.bigmodel.cn`\n- Override host via Providers → z.ai → *API region* or `Z_AI_API_HOST=open.bigmodel.cn`.\n- Override the full quota URL (e.g. coding plan endpoint) via `Z_AI_QUOTA_URL=https://open.bigmodel.cn/api/coding/paas/v4`.\n- Headers:\n  - `authorization: Bearer <token>`\n  - `accept: application/json`\n\n## Parsing + mapping\n- Response fields:\n  - `data.limits[]` → each limit entry.\n  - `data.planName` (or `plan`, `plan_type`, `packageName`) → plan label.\n- Limit types:\n  - `TOKENS_LIMIT` → primary (tokens window).\n  - `TIME_LIMIT` → secondary (MCP/time window) if tokens also present.\n- Window duration:\n  - Unit + number → minutes/hours/days.\n- Reset:\n  - `nextResetTime` (epoch ms) → date.\n- Usage details:\n  - `usageDetails[]` per model (MCP usage list).\n\n## Key files\n- `Sources/CodexBarCore/Providers/Zai/ZaiUsageStats.swift`\n- `Sources/CodexBarCore/Providers/Zai/ZaiSettingsReader.swift`\n- `Sources/CodexBar/ZaiTokenStore.swift` (legacy migration helper)\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"codexbar\",\n  \"private\": true,\n  \"scripts\": {\n    \"start\": \"./Scripts/compile_and_run.sh\",\n    \"start:debug\": \"./Scripts/compile_and_run.sh\",\n    \"start:release\": \"sh -c './Scripts/package_app.sh release && (pkill -x CodexBar || pkill -f CodexBar.app || true) && cd /Users/steipete/Projects/codexbar && open -n /Users/steipete/Projects/codexbar/CodexBar.app'\",\n    \"lint\": \"./Scripts/lint.sh lint\",\n    \"format\": \"./Scripts/lint.sh format\",\n    \"check\": \"./Scripts/lint.sh lint\",\n    \"docs:list\": \"node Scripts/docs-list.mjs\",\n    \"build\": \"swift build\",\n    \"test\": \"swift test\",\n    \"test:tty\": \"swift test --filter TTYIntegrationTests\",\n    \"test:live\": \"LIVE_TEST=1 swift test --filter LiveAccountTests\",\n    \"release\": \"./Scripts/package_app.sh release\",\n    \"restart\": \"pnpm start\",\n    \"stop\": \"pkill -x CodexBar || pkill -f CodexBar.app || true\"\n  }\n}\n"
  }
]